<template>
  <div class="voice-chat-container">
  <div class="voice-chat-panel" :class="{ 
    'mobile': isMobile,
    'expanded': isExpanded,
    'minimized': (isMobile && !isExpanded) || (!isMobile && isMinimized)
  }">
    <!-- 语音聊天头部 -->
    <div class="voice-header">
      <h3 @click="isMobile ? toggleExpand() : toggleMinimize()">{{ isMinimized && !isMobile ? '🎙️' : '🎙️ 语音聊天' }}</h3>
      <div class="voice-controls">
        <button 
          @click.stop="toggleMicrophone" 
          :class="['control-btn', 'mic-btn', { active: microphoneEnabled, muted: isMuted }]"
          :disabled="isMuted"
        >
          {{ microphoneEnabled && !isMuted ? 'MIC' : 'OFF' }}
        </button>
        
        <!-- 麦克风音量控制 -->
        <div 
          class="mic-volume-control" 
          v-if="microphoneEnabled && !isMuted"
          :title="`麦克风音量: ${Math.round(microphoneVolume * 100)}%`"
        >
          <input 
            type="range" 
            min="0" 
            max="100" 
            :value="Math.round(microphoneVolume * 100)"
            @input="setMicrophoneVolume($event.target.value / 100)"
            class="mic-volume-slider"
          >
        </div>
        
        
        
        <!-- 桌面端收起按钮 -->
        <button 
          v-if="!isMobile"
          @click.stop="toggleMinimize"
          class="control-btn"
        >
          {{ isMinimized ? '▲' : '▼' }}
        </button>
        
        <!-- 移动端展开/收起按钮 -->
        <button 
          v-if="isMobile" 
          @click.stop="toggleExpand"
          class="control-btn"
        >
          {{ isExpanded ? '−' : '+' }}
        </button>
      </div>
    </div>
    
    <!-- 页签导航 -->
    <div class="tabs-navigation" v-show="(!isMobile && !isMinimized) || (isMobile && isExpanded)">
      <div class="tabs-nav">
        <button 
          @click="switchMainTab('public')"
          class="main-tab-btn"
          :class="{ active: activeMainTab === 'public' }"
        >
          📢 公共聊天
        </button>
        <button 
          @click="switchMainTab('private')"
          class="main-tab-btn"
          :class="{ active: activeMainTab === 'private' }"
        >
          👥 私人聊天
          <span v-if="activePrivateChats.length > 0" class="tab-badge">{{ activePrivateChats.length }}</span>
        </button>

        <button 
          v-if="isStoryteller"
          @click="switchMainTab('control')"
          class="main-tab-btn"
          :class="{ active: activeMainTab === 'control' }"
        >
          🎛️ 语音控制
        </button>
      </div>
    </div>

    <!-- 主要内容区域 -->
    <div class="voice-content" v-show="(!isMobile && !isMinimized) || (isMobile && isExpanded)">
      <!-- 全局状态显示 -->
      <div class="global-status">
      <div v-if="globalMute" class="status-warning">
        🔇 全局禁音已开启
      </div>
      <div v-if="!publicChatEnabled && !privateChatEnabled" class="status-warning">
        ❌ 语音聊天已关闭
      </div>
    </div>

      <!-- 页签内容区域 -->
      <div class="tab-content-area">
        <!-- 公共聊天页签内容 -->
        <div v-show="activeMainTab === 'public'" class="main-tab-content public-tab">

    <!-- 公共聊天室 -->
    <div class="chat-room public-chat" v-if="publicChatEnabled && !globalMute">
      <div class="room-header">
        <span class="room-title">📢 公共聊天</span>
        <div class="room-status">
          <span class="participant-count">{{ publicChatParticipants.length }} 人</span>
          <span v-if="getPlayersInPrivateChats().length > 0" class="private-chat-indicator" 
                :title="`${getPlayersInPrivateChats().length} 人正在私聊中`">
            💬 {{ getPlayersInPrivateChats().length }}
          </span>
          <div 
            :class="['connection-indicator', publicChatStatus]"
            :title="getConnectionStatusText(publicChatStatus)"
          ></div>
        </div>
      </div>
      
      <!-- 没有参与者时的提示 -->
      <div v-if="publicChatParticipants.length === 0" class="no-participants">
        <span v-if="getPlayersInPrivateChats().length === 0">🔇 公共聊天室暂无参与者</span>
        <span v-else>🔇 所有玩家都在私聊中</span>
        <p v-if="getPlayersInPrivateChats().length === 0">点击下方按钮加入公共聊天</p>
        <p v-else>
          有 {{ getPlayersInPrivateChats().length }} 人正在私聊，
          <span v-if="!isInPublicChat">你可以加入公共聊天等待</span>
          <span v-else>可切换到私人聊天页签查看详情</span>
        </p>
      </div>
      
      <div class="participants-list">
        <div 
          v-for="participant in publicChatParticipants" 
          :key="participant.id"
          class="participant"
          :class="{ 
            speaking: isSpeaking(participant.id), 
            muted: isPlayerMuted(participant.id),
            self: participant.id === currentPlayerId
          }"
        >
          <span class="participant-name">{{ participant.name }}</span>
          <div class="participant-controls">
            <span v-if="isSpeaking(participant.id)" class="speaking-indicator">🗣️</span>
            <span v-if="isPlayerMuted(participant.id)" class="mute-indicator">🔇</span>
            <div 
              :class="['signal-strength', getSignalStrength(participant.id)]"
              :title="`信号强度: ${getSignalStrength(participant.id)}, 延迟: ${getConnectionPing(participant.id)}ms`"
            >
              📶
            </div>
            <!-- 音量控制 -->
            <div class="volume-control" v-if="participant.id !== currentPlayerId">
              <input 
                type="range" 
                min="0" 
                max="100" 
                :value="getRemoteVolume(participant.id)"
                @input="setRemoteVolume(participant.id, $event.target.value)"
                class="volume-slider"
                :title="`音量: ${getRemoteVolume(participant.id)}%`"
              >
              <span class="volume-icon">🔊</span>
            </div>
          </div>
        </div>
      </div>
      
      <div class="room-controls">
        <template v-if="isInAnyPrivateChat">
          <div class="locked-tip">🔒 已在私聊中，当前无法操作</div>
        </template>
        <template v-else>
          <button 
            @click="joinPublicChat" 
            v-if="!isInPublicChat"
            class="join-btn"
            :disabled="!canJoinPublicChat"
          >
            🔊 加入公共聊天
          </button>
          <button 
            @click="leavePublicChat" 
            v-else
            class="leave-btn"
          >
            🔇 离开公共聊天
          </button>
        </template>
      </div>
      </div>
    </div>

        <!-- 私人聊天页签内容 -->
        <div v-show="activeMainTab === 'private'" class="main-tab-content private-tab">

    <!-- 私人聊天列表 -->
    <div class="private-chats" v-if="privateChatEnabled && !globalMute">
      <div class="section-header">
        <span>👥 私人聊天</span>
        <div class="header-status">
          <span v-if="getPlayersInPrivateChats().length > 0" class="moved-players-info"
                :title="'这些玩家已从公聊移动到私聊中'">
            📤 已移动 {{ getPlayersInPrivateChats().length }} 人
          </span>
        <button @click="openPrivateChatDialog" class="add-private-btn" title="发起多人私聊">+</button>
      </div>
      </div>

      <!-- 没有活跃私聊时的提示 -->
      <div v-if="allActivePrivateChats.length === 0 && getPlayersInPrivateChats().length === 0" class="no-private-chats">
        <span>💬 暂无私人聊天</span>
        <p>点击 "+" 按钮发起私聊申请</p>
      </div>
      
            <!-- 调试信息：当状态持续不一致时显示（延迟3秒） -->
      <div v-if="shouldShowInconsistencyWarning" class="debug-warning" style="background: yellow; padding: 10px; margin: 10px 0; border-radius: 4px; color: black;">
        ⚠️ 状态持续不一致: 有 {{ getPlayersInPrivateChats().length }} 人在私聊，但没有活跃房间 ({{ allActivePrivateChats.length }})
        <br>私聊玩家: {{ getPlayersInPrivateChats().join(', ') }}
        <br>活跃房间: {{ allActivePrivateChats.length }}
        <br>不一致持续时间: {{ inconsistencyDuration }}ms
        <br><button @click="debugPrivateChatState" style="background: red; color: white; padding: 5px; margin-top: 5px;">🔍 强制检查状态</button>
        <br><button @click="forceRefreshAllChats" style="background: blue; color: white; padding: 5px; margin-top: 5px;">🔄 强制刷新所有</button>
      </div>

      <!-- 活跃的私人聊天 -->
        <div class="private-chats-container">
      <div 
        v-for="chat in allActivePrivateChats" 
        :key="chat.roomId"
        class="private-chat-room"
        :class="{ expanded: expandedParticipantsLists[chat.roomId] }"
        :data-room-id="chat.roomId"
      >
        <div class="private-room-header">
            <div 
              class="room-id-info" 
              @click="toggleParticipantsList(chat.roomId)"
              :title="'点击展开/收起房间参与者'"
            >
              <span class="room-name">{{ getRoomDisplayName(chat) }}</span>
              <span class="expand-icon">
                {{ expandedParticipantsLists[chat.roomId] ? '▼' : '▶' }}
          </span>
            </div>
          <div class="private-controls">
            <button 
              v-if="!chat.participants.includes(currentPlayerId)"
              @click.stop="applyJoinPrivate(chat.roomId)"
              class="apply-join-btn"
              title="申请加入该私聊房间"
            >
              申请加入
            </button>
            <button 
                @click.stop="returnToPublicChat(chat.roomId)" 
              class="return-public-btn"
              title="退出私聊，回到公聊"
            >
              📢
            </button>
              <button 
                @click.stop="leavePrivateChat(chat.roomId)" 
                class="leave-private-btn" 
                title="离开私聊"
              >
                ❌
              </button>
          </div>
        </div>
        
          <!-- 可滑动的参与者列表 -->
          <div 
            v-show="expandedParticipantsLists[chat.roomId]"
            class="participants-container"
          >
            <div class="participants-scroll-area">
              <div class="participants-header">
                <span class="header-title">👥 正在聊天的玩家</span>
                <span class="connection-status">{{ getPrivateChatConnectionStatus(chat.roomId) }}</span>
          </div>
              <div class="participants-list">
            <div 
              v-for="participantId in chat.participants" 
              :key="participantId"
                  class="participant-item"
              :class="{ 
                speaking: isSpeaking(participantId),
                self: participantId === currentPlayerId
              }"
            >
                  <div class="participant-info">
              <span class="participant-name">{{ getPlayerName(participantId) }}</span>
                    <div class="participant-badges">
                      <span v-if="participantId === currentPlayerId" class="self-badge">我</span>
                      <span v-if="isSpeaking(participantId)" class="speaking-badge">🗣️</span>
                    </div>
                  </div>
              <div class="participant-status">
                <div 
                  :class="['signal-strength', getSignalStrength(participantId)]"
                  :title="`信号强度: ${getSignalStrength(participantId)}, 延迟: ${getConnectionPing(participantId)}ms`"
                >
                  📶
                </div>
                    <div class="volume-indicator">
                      <div class="volume-bar" :style="{ width: getParticipantVolume(participantId) + '%' }"></div>
                    </div>
                  </div>
                </div>
              </div>
              
              <!-- 聊天统计信息 -->
              <div class="chat-stats">
                <div class="stat-item">
                  <span class="stat-label">持续时间:</span>
                  <span class="stat-value">{{ getPrivateChatDuration(chat.roomId) }}</span>
                </div>
                <div class="stat-item">
                  <span class="stat-label">连接状态:</span>
                  <span class="stat-value" :class="getPrivateChatStatus(chat.roomId)">
                    {{ getChatStatusText(chat.roomId) }}
                  </span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- 迁移：私聊申请通知已移除，改为中心模态 -->
    </div>



        </div>

        

        <!-- 语音控制页签内容（仅说书人可见） -->
        <div v-show="activeMainTab === 'control'" class="main-tab-content control-tab" v-if="isStoryteller">
          <!-- 个人音频控制 -->
          <div class="personal-control-section">
            <h4>🎤 个人控制</h4>
            <div class="personal-controls">
              <div class="mic-status">
                <span class="status-label">麦克风:</span>
                <span class="status-value" :class="{ active: microphoneEnabled }">
                  {{ microphoneEnabled ? '🎤 开启' : '🔇 关闭' }}
                </span>
              </div>
              <div class="volume-control" v-if="microphoneEnabled">
                <span class="volume-label">音量:</span>
                <input 
                  type="range" 
                  min="0" 
                  max="100" 
                  :value="Math.round(microphoneVolume * 100)"
                  @input="setMicrophoneVolume($event.target.value / 100)"
                  class="volume-slider"
                />
                <span class="volume-value">{{ Math.round(microphoneVolume * 100) }}%</span>
              </div>
        </div>
      </div>
      
          <!-- 全局控制 -->
          <div class="voice-control-section">
            <h4>🎛️ 全局控制</h4>
            <!-- 删除全局状态条，顶部已有提示 -->
            <div class="global-controls">
              <button 
                @click="muteAllPlayers" 
                :disabled="globalMute"
                :class="['control-action-btn', 'mute-all-btn', { active: globalMute }]"
                title="静音所有玩家"
              >
                🔇 全员静音
              </button>
              <button 
                @click="unmuteAllPlayers" 
                :disabled="!globalMute"
                :class="['control-action-btn', 'unmute-all-btn', { active: !globalMute }]"
                title="取消所有玩家静音"
              >
                🔊 取消静音
              </button>
            </div>
      </div>
      
          <!-- 玩家控制 -->
          <div class="player-control-section">
            <h4>👥 玩家控制</h4>
            <div class="players-control-list">
              <div v-if="controllablePlayers.length === 0" class="no-controllable">
                暂无可控制的玩家
              </div>
              <div 
                v-for="player in controllablePlayers" 
                :key="player.id"
                class="player-control-item"
          :class="{ 
                  muted: isPlayerMuted(player.id),
                  speaking: isSpeaking(player.id)
                }"
              >
                <span class="player-name">{{ player.name }}</span>
                <div class="player-actions">
                  <button 
                    @click="togglePlayerMute(player.id)"
                    class="action-btn"
                    :disabled="false"
                    :class="{ active: isPlayerMuted(player.id), blocked: globalMute }"
                    :title="globalMute ? '全员禁言中，无法单独解禁' : (isPlayerMuted(player.id) ? '取消禁言' : '禁言')"
                  >
                    {{ isPlayerMuted(player.id) ? '🔈 取消' : '🔇 禁言' }}
                  </button>
            </div>
            </div>
          </div>
          </div>
        </div>
      </div>
    </div>

      <!-- 私聊申请对话框 -->
    <PrivateChatDialog 
      v-if="showPrivateChatDialog"
      @close="showPrivateChatDialog = false"
        @request="sendPrivateChatMulti"
        @directConnect="createDirectPrivateChat"
    />

      <!-- 阻塞式模态确认：申请加入房间（申请者侧确认发送） -->
      <Modal v-if="joinConfirm.visible" @close="joinConfirm.visible=false">
        <div style="min-width:280px; padding:10px; color:white; text-align:center;">
          <h3 style="margin:8px 0;">申请加入私聊</h3>
          <p style="opacity:0.9;">是否确认向房主发送加入申请？</p>
          <div style="display:flex; gap:10px; justify-content:center; margin-top:10px;">
            <button class="control-btn" @click="confirmJoinApply(false)">取消</button>
            <button class="control-btn" @click="confirmJoinApply(true)">确认</button>
          </div>
        </div>
      </Modal>

      <!-- 中心模态：收到房主的私聊邀请（被邀请者确认加入） -->
      <Modal v-if="inviteModalVisible" @close="respondInvite(false)">
        <div style="min-width:300px; padding:12px; color:white; text-align:center;">
          <h3 style="margin:8px 0;">加入私聊邀请</h3>
          <p style="opacity:0.9;">{{ incomingInviteOwnerName }} 邀请你加入一个私人语音房间，是否加入？</p>
          <div style="display:flex; gap:10px; justify-content:center; margin-top:12px;">
            <button class="control-btn" @click="respondInvite(false)">拒绝</button>
            <button class="control-btn" @click="respondInvite(true)">加入</button>
          </div>
        </div>
      </Modal>

      <!-- 中心模态：房主审批加入请求 -->
      <Modal v-if="joinApprovalVisible" @close="respondJoinApproval(false)">
        <div style="min-width:300px; padding:12px; color:white; text-align:center;">
          <h3 style="margin:8px 0;">有玩家申请加入</h3>
          <p style="opacity:0.9;">{{ joinApprovalRequesterName }} 申请加入你的私人语音房间，是否同意？</p>
          <div style="display:flex; gap:10px; justify-content:center; margin-top:12px;">
            <button class="control-btn" @click="respondJoinApproval(false)">拒绝</button>
            <button class="control-btn" @click="respondJoinApproval(true)">同意</button>
          </div>
        </div>
      </Modal>

    <!-- 单人房间提示对话框 -->
    <div v-if="showAloneRoomDialog" class="alone-room-dialog">
      <div class="dialog-overlay" @click="handleAloneRoomChoice"></div>
      <div class="dialog-content">
        <div class="dialog-header">
          <h3>🚪 房间提示</h3>
        </div>
        <div class="dialog-body">
          <div class="alone-room-message">
            <div class="message-icon">👥</div>
            <div class="message-text">
              <p class="main-message" v-if="aloneRoomInviteInfo">
                {{ aloneRoomInviteInfo.inviterName }} 邀请了 {{ aloneRoomInviteInfo.inviteeName }}
              </p>
              <p class="main-message" v-else>
                房间中只剩你一个人了
              </p>
              <p class="sub-message" v-if="aloneRoomInviteInfo">
                他离开了此房间，确认后你将回到公聊频道
              </p>
              <p class="sub-message" v-else>
                其他参与者已经离开，你已自动回到公聊频道
              </p>
            </div>
          </div>
        </div>
        <div class="dialog-footer">
          <button @click="handleAloneRoomChoice" class="confirm-btn single-btn">
            ✅ 确认
          </button>
        </div>
      </div>
    </div>

      <!-- 中心浮层提示（响应式元素，确保scoped样式生效） -->
      <div v-if="centerBanner.show" class="center-voice-banner show">{{ centerBanner.text }}</div>
      <!-- 测试模式指示器（已取消可视化提示） -->
    </div>
      
    
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import PrivateChatDialog from './PrivateChatDialog.vue';
import Modal from './modals/Modal.vue';
import VoiceEngine from '../utils/VoiceEngine.js';

export default {
  name: 'VoiceChatPanel',
  components: {
    PrivateChatDialog,
    Modal
  },
  data() {
    return {
      microphoneEnabled: false,
      showPrivateChatDialog: false,
      joinConfirm: { visible: false, roomId: null, requesterId: null, type: '' },
      speakingUsers: new Set(), // 正在说话的用户
      connectionQualities: new Map(), // 连接质量缓存
      isExpanded: false, // 移动端展开状态
      isMinimized: false, // 桌面端收起状态
      isMobile: false, // 是否移动端
      voiceActivityTimer: null, // 语音活动模拟定时器
      expandedParticipantsLists: {}, // 展开的参与者列表（私聊房间ID -> boolean）
      expandedRoomsCount: 0, // 强制响应式更新的计数器
      connectionMonitorTimer: null, // 连接质量监控定时器
      voiceEngine: null, // 语音引擎实例
      microphoneVolume: 0.8, // 麦克风音量
      remoteVolumes: new Map(), // 远程音频音量 playerId -> volume
      isMonitorExpanded: true, // 私聊监控区域展开状态
      activeTab: 'voice', // 当前活跃的页签：'voice' 或 'monitor'
      activeMainTab: 'public', // 主面板当前活跃的页签：'public', 'private', 'monitor', 'control'
      privateChatPlayersCache: [], // 缓存在私聊中的玩家列表，避免频繁计算
      lastCacheUpdate: 0, // 上次缓存更新时间
      uiRefreshTimer: null, // UI状态一致性检查定时器
      inconsistencyStartTime: null, // 状态不一致开始时间
      inconsistencyCheckTimer: null, // 状态不一致检查定时器
      showAloneRoomDialog: false, // 是否显示单人房间提示对话框
      aloneRoomId: null, // 当前单人房间的ID
      roomCheckTimer: null, // 房间人数检查定时器
      roomInviteInfo: new Map(), // 房间邀请信息 roomId -> {inviter, invitee, inviterName, inviteeName}
      aloneRoomInviteInfo: null, // 当前单人房间的邀请信息
      centerBanner: { show: false, text: '' },
      // 本地控制模态显示（配合store）
      inviteModalState: { visible: false },
      joinApprovalState: { visible: false }
    };
  },
  computed: {
    ...mapState('voiceChat', [
      'permissions', 
      'chatRooms', 
      'playerPermissions',
      'testMode',
      'storytellerPanel',
      'displayNames',
      'incomingInvite',
      'joinApproval',
      'users',
      'rooms',
      'publicRoomId',
      'roomStats'
    ]),
    ...mapState('players', ['players']),
    ...mapState('session', ['playerId']),
    ...mapGetters('voiceChat', [
      'currentUserRole',
      'currentUserPermissions', 
      'activeChatRooms',
      'pendingPrivateChatRequests',
      'connectionQualityStats'
    ]),
    // 监听Store触发的居中提示（来自socket广播）
    uiBannerCounter() {
      return this.$store.state.voiceChat.uiBannerCounter;
    },
    
    isStoryteller() {
      return this.currentUserRole === 'storyteller';
    },
    
    currentPlayerId() {
      return this.playerId;
    },
    
    // 全局权限
    globalMute() {
      return this.permissions.globalMute;
    },
    
    publicChatEnabled() {
      return this.permissions.publicChatEnabled;
    },
    
    privateChatEnabled() {
      return this.permissions.privateChatEnabled;
    },
    
    // 当前用户状态
    isMuted() {
      return this.currentUserPermissions.isMuted || this.globalMute;
    },
    
    canJoinPublicChat() {
      return this.currentUserPermissions.canPublicChat && !this.isMuted;
    },
    
    // 是否在任意私聊房间中
    isInAnyPrivateChat() {
      if (!this.chatRooms || !this.chatRooms.private) return false;
      let inPrivate = false;
      if (this.chatRooms.private instanceof Map) {
        this.chatRooms.private.forEach((room) => {
          if (room && room.active && Array.isArray(room.participants) && room.participants.includes(this.currentPlayerId)) {
            inPrivate = true;
          }
        });
      }
      return inPrivate;
    },

    // 公共聊天
    isInPublicChat() {
      return this.chatRooms.public.active && 
             this.chatRooms.public.participants.includes(this.currentPlayerId);
    },
    
    publicChatParticipants() {
      // 优先使用新的房间管理数据
      if (this.rooms && this.publicRoomId && this.rooms.has(this.publicRoomId)) {
        const publicRoom = this.rooms.get(this.publicRoomId);
        if (publicRoom && publicRoom.status === 'active') {
          return publicRoom.participants.map(id => {
            const player = this.players.find(p => p.id === id);
            if (player) {
              return player;
            }
            
            const playerName = this.getPlayerName(id);
            return { id, name: playerName };
          });
        }
      }
      
      // 兼容性：使用旧的chatRooms数据
      if (!this.chatRooms.public.active) return [];
      
      const validParticipants = this.chatRooms.public.participants.filter(id => 
        this.shouldShowInPublicChat(id)
      );
      
      return validParticipants.map(id => {
        const player = this.players.find(p => p.id === id);
        if (player) {
          return player;
        }
        
        const playerName = this.getPlayerName(id);
        return { id, name: playerName };
      });
    },

    // 说书人可控制的玩家（过滤掉自己与无ID/未入座玩家）
    controllablePlayers() {
      if (!Array.isArray(this.players)) return [];
      return this.players
        .filter(p => p && p.id && p.id !== this.currentPlayerId)
        .map(p => ({ id: p.id, name: p.name || this.getPlayerName(p.id) }));
    },
    
    publicChatStatus() {
      // 基于参与者连接质量计算整体状态
      const qualities = this.publicChatParticipants.map(p => 
        this.getSignalStrength(p.id)
      );
      
      if (qualities.every(q => q === 'excellent')) return 'excellent';
      if (qualities.some(q => q === 'poor')) return 'poor';
      return 'good';
    },
    
    // 私人聊天
    activePrivateChats() {
      if (!this.chatRooms || !this.chatRooms.private) {
        return [];
      }
      
      const chats = [];
      this.chatRooms.private.forEach((room, roomId) => {
        if (room.active && room.participants.includes(this.currentPlayerId)) {
          chats.push({ ...room, roomId });
        }
      });
      
      return chats;
    },
    
    // 所有活跃的私聊（包括其他人的）
    allActivePrivateChats() {
      // 添加关键的响应式依赖
      this.expandedRoomsCount; // 强制依赖，用于手动触发更新
      this.chatRooms; // 基础依赖
      
      this.$config.logger.debug('🔍 计算 allActivePrivateChats, expandedRoomsCount:', this.expandedRoomsCount);
      
      if (!this.chatRooms || !this.chatRooms.private) {
        this.$config.logger.debug('❌ 没有聊天室数据');
        return [];
      }
      
      const chats = [];
      
      if (this.chatRooms.private instanceof Map) {
      this.chatRooms.private.forEach((room, roomId) => {
          this.$config.logger.debug('🔍 allActivePrivateChats 检查房间:', roomId, room);
          // 显示至少1人的活跃房间（说书人新建房间时仅自己在内也应可见）
          if (room.active && 
              room.participants && 
              Array.isArray(room.participants) && 
              room.participants.length >= 1) {
            this.$config.logger.debug('🔍 房间符合条件，添加到列表');
            const chatItem = { 
            ...room, 
            roomId,
            createdAt: room.createdAt || Date.now()
            };
            chats.push(chatItem);
            this.$config.logger.debug('🔍 已添加到chats数组:', chatItem);
            this.$config.logger.debug('🔍 当前chats数组长度:', chats.length);
          } else {
            this.$config.logger.debug('🔍 房间不符合条件:', {
              active: room.active,
              hasParticipants: !!room.participants,
              isArray: Array.isArray(room.participants),
              length: room.participants?.length
          });
        }
      });
      }
      
      this.$config.logger.debug('🔍 allActivePrivateChats 最终结果:', chats);
      
      // 按创建时间排序，最新的在前
      return chats.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
    },
    
    pendingRequests() {
      return this.pendingPrivateChatRequests.filter(req => 
        req.toId === this.currentPlayerId
      );
    },
    
    storytellerPanelVisible() {
      return this.storytellerPanel.visible;
    },
    
    // 是否应该显示状态不一致警告
    shouldShowInconsistencyWarning() {
      const hasInconsistency = this.allActivePrivateChats.length === 0 && this.getPlayersInPrivateChats().length > 0;
      return hasInconsistency && this.inconsistencyStartTime && (Date.now() - this.inconsistencyStartTime > 3000);
    },
    
    // 不一致持续时间
    inconsistencyDuration() {
      if (this.inconsistencyStartTime) {
        return Date.now() - this.inconsistencyStartTime;
      }
      return 0;
    },

    // 邀请模态可见性与信息
    inviteModalVisible() {
      return !!this.incomingInvite && this.inviteModalState?.visible;
    },
    incomingInviteOwnerName() {
      return this.incomingInvite?.ownerName || '玩家';
    },

    // 房主审批模态可见性与信息
    joinApprovalVisible() {
      return !!this.joinApproval && this.joinApprovalState?.visible;
    },
    joinApprovalRequesterName() {
      return this.joinApproval?.requesterName || '玩家';
    }
  },
  
  watch: {
    // 来自store的居中提示
    uiBannerCounter() {
      const text = this.$store.state.voiceChat.uiBannerText;
      if (text) this._showCenterBanner(text);
    },
    // 监听玩家数据变化，确保名称显示正确
    players: {
      handler(newPlayers) {
        this.$config.logger.debug('👥 玩家数据发生变化');
        this.$config.logger.debug('新玩家数据:', newPlayers.map(p => ({ id: p.id, name: p.name })));
        
        // 如果有参与者名字需要更新，强制重新渲染
        if (this.chatRooms.public.participants.length > 0) {
          this.$config.logger.debug('🔄 公聊参与者名字可能需要更新');
        }
      },
      deep: true,
      immediate: false
    },
    
    // 监听聊天室数据变化，强制更新UI
    chatRooms: {
      handler() {
        this.$config.logger.debug('🏠 聊天室数据发生变化');
        
        // 立即清除缓存并强制两个方法同步
        this.clearPrivateChatPlayersCache();
        
        // 强制重新计算 getPlayersInPrivateChats
        this.getPlayersInPrivateChats();
        
        // 强制触发 allActivePrivateChats 重新计算
        this.expandedRoomsCount++;
        this.$forceUpdate();
        
        // 检查状态一致性
        this.checkInconsistencyState();
        
        this.$config.logger.debug('🔄 聊天室变化：立即UI更新完成');
      },
      deep: true,
      immediate: false
    },
    
    // 监听计算属性变化来检查状态一致性
    allActivePrivateChats: {
      handler() {
        this.checkInconsistencyState();
      },
      immediate: false
    }
  },
  
  methods: {
    _applyOptimisticRole() {
      const myId = this.currentPlayerId;
      if (!myId) return;
      const isStory = this.$store.state.session.isSpectator === true;
      const hasRole = this.$store.state.voiceChat.roles && this.$store.state.voiceChat.roles.get && this.$store.state.voiceChat.roles.get(myId);
      if (!hasRole) {
        this.$store.commit('voiceChat/setRole', { playerId: myId, role: isStory ? 'storyteller' : 'player' });
      }
    },
    openPrivateChatDialog() {
      // 完全依赖服务器端用户数据判断权限
      const myId = this.currentPlayerId;
      const currentUser = this.$store.state.voiceChat.users?.get(myId);
      const userRole = currentUser?.role;
      
      // 必须等待服务器用户数据
      if (!currentUser) {
        this._showCenterBanner('等待服务器数据...');
        return;
      }
      
      // 检查权限：说书人和玩家可以发起私聊，访客不可以
      if (userRole === 'spectator') {
        this._showCenterBanner('访客无法发起私聊，请先认领座位');
        return;
      }
      
      this.showPrivateChatDialog = true;
    },
    // 工具方法
    getPlayerName(playerId) {
      // 明确的说书人占位符
      if (playerId === 'storyteller') {
        return this.getStorytellerDisplayName();
      }

      // 0) 优先使用新的用户管理数据
      const users = this.$store.state.voiceChat.users;
      if (users && users.has && users.has(playerId)) {
        const user = users.get(playerId);
        if (user && user.displayName) {
          return user.displayName;
        }
      }

      // 兼容性：服务器角色判断
      const roleMap = this.$store.state.voiceChat.roles;
      const isStoryByRole = roleMap && roleMap.get && roleMap.get(playerId) === 'storyteller';
      if (isStoryByRole || playerId === 'host') {
        return this.getStorytellerDisplayName();
      }

      // 1) 优先用玩家列表里的正式名称（已入座）
      const player = this.players.find(p => p.id === playerId);
      if (player && player.name) {
        return player.name;
      }

      const isSelf = playerId === this.currentPlayerId;
      if (isSelf) {
        // 自己：仅当服务器角色为说书人或ID为host时才显示说书人，否则按访客处理
        return isStoryByRole ? this.getStorytellerDisplayName() : '【访客】';
      }

      // 2) 服务器同步的显示名（若误含“说书人”但ID非host且角色非说书人，则降级为访客）
      if (this.displayNames && this.displayNames.get && this.displayNames.has(playerId)) {
        const dn = this.displayNames.get(playerId);
        if (dn && dn.trim()) {
          if (!isStoryByRole && playerId !== 'host' && /说书人|storyteller/i.test(dn)) {
            return '【访客】';
          }
          return dn.trim();
        }
      }

      // fallback：未入座显示访客
      return '【访客】';
        },

    // 移除启发式说书人判断，完全依赖服务器角色
    
    // 处理私聊更新事件（玩家离开等）
    handlePrivateChatUpdated(data) {
      this.$config.logger.debug('🔄 收到私聊更新事件:', data);
      
      if (data.action === 'player_left') {
        this.$config.logger.debug(`玩家 ${data.playerName} 离开了房间 ${data.roomId}`);
        
        // 强制刷新UI，确保私聊面板立即更新
        this.clearPrivateChatPlayersCache();
        this.$forceUpdate();
        
        // 使用nextTick确保状态更新后再次刷新
        this.$nextTick(() => {
          this.$config.logger.debug('🔄 私聊面板UI已强制刷新');
          this.$forceUpdate();
        });
        
        // 检查当前用户是否受影响
        const currentPrivateRooms = Object.keys(this.chatRooms.private || {});
        this.$config.logger.debug('🔍 当前私聊房间:', currentPrivateRooms);
        
        // 显示提示信息
        this.showToast('info', `${data.playerName} 已离开私聊房间`);
      }
    },
    
    // 获取说书人显示名称（包含星级）
    getStorytellerDisplayName() {
      const savedName = localStorage.getItem('storyteller_name') || '';
      const savedStarLevel = localStorage.getItem('storyteller_star_level') || 'trainee';
      
      const starLevels = {
        'trainee': '🌟',
        'one_star': '⭐',
        'two_star': '⭐⭐'
      };
      
      const starIcon = starLevels[savedStarLevel] || '🌟';
      
      if (savedName.trim()) {
        return `【${starIcon}】说书人·${savedName.trim()}`;
      } else {
        return `【${starIcon}】说书人`;
      }
    },

    // 获取房间的友好显示名称
    getRoomDisplayName(chat) {
      if (!chat.participants || chat.participants.length === 0) {
        return '空房间';
      }
      
      // 生成格式：【玩家名】和【玩家名】正在私聊
      const participantNames = chat.participants.map(id => this.getPlayerName(id));
      
      if (participantNames.length === 1) {
        return `【${participantNames[0]}】独自在房间`;
      } else if (participantNames.length === 2) {
        return `【${participantNames[0]}】和【${participantNames[1]}】正在私聊`;
      } else {
        // 多人房间
        return `【${participantNames.join('】、【')}】正在私聊`;
      }
    },
    ...mapActions('voiceChat', [
      'initializeVoiceChat',
      'requestPrivateChat',
      'handlePrivateChatRequest',
      'mutePlayer',
      'setGlobalMute',
      'monitorConnectionQuality'
    ]),

    // 申请加入私聊
    applyJoinPrivate(roomId) {
      // 限制：未入座玩家不可申请
      const isSeated = !!this.players.find(p => p.id === this.currentPlayerId);
      if (!isSeated) {
        this._showCenterBanner('请先入座后再申请加入私聊房间');
        return;
      }
      this.joinConfirm = { visible: true, roomId, requesterId: this.currentPlayerId, type: 'apply' };
    },

    // 确认加入（弹窗回调）
    confirmJoinApply(approved) {
      const { roomId } = this.joinConfirm;
      this.joinConfirm.visible = false;
      if (!roomId) return;
      if (approved) {
        // 再次校验入座（双保险）
        const isSeated = !!this.players.find(p => p.id === this.currentPlayerId);
        if (!isSeated) {
          this._showCenterBanner('请先入座后再申请加入私聊房间');
          return;
        }
        // 向房主发送加入请求（socket广播）
        this.sendVoiceChatMessage('voice_private_join_request', {
          roomId,
          requesterId: this.currentPlayerId,
        });
        this._showCenterBanner('已发送加入申请，等待房主同意');
      }
    },
    
    // Toast消息的安全显示方法
    showToast(type, message) {
      if (this.$toast && this.$toast[type]) {
        this.$toast[type](message);
      } else {
        this.$config.logger.debug(`[${type.toUpperCase()}] ${message}`);
      }
    },
    
    // WebSocket消息发送的通用方法
    sendVoiceChatMessage(command, data) {
      const socket = this.$store.state.socket._socket;
      if (!socket || socket.readyState !== WebSocket.OPEN) {
        this.$config.logger.warn('⚠️ WebSocket未连接，无法发送语音聊天消息:', command, data);
        this.showToast('warning', '连接服务器失败，操作未能完成');
        return false;
      }
      
      try {
        socket.send(JSON.stringify([command, data]));
        return true;
      } catch (error) {
        this.$config.logger.error('❌ 发送WebSocket消息失败:', error);
        this.showToast('error', '发送消息失败');
        return false;
      }
    },

    // 发送多人邀请请求
    sendPrivateChatMulti(targetIds) {
      if (!Array.isArray(targetIds) || targetIds.length === 0) return;
      // 本地立即创建房间并进入（以房主为准），随后广播给其他客户端
      const roomId = `private_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;
      const ownerId = this.currentPlayerId; // 不再使用 'host' 作为占位ID
      const createdAt = Date.now();
      const payload = { roomId, ownerId, targetIds };

      // 本地创建房间（UI 立即可见）
      this.$store.commit('voiceChat/updateChatRoom', {
        type: 'private',
        roomId,
        data: {
          active: true,
          ownerId,
          participants: [this.currentPlayerId],
          pendingInvites: targetIds,
          joinRequests: [],
          createdAt,
          joinPolicy: 'ownerApproval'
        }
      });

      // 触发事件总线，驱动本机先离开公聊并加入新建的私聊房间
      if (window.voiceChatEventBus) {
        const inviteInfo = {
          inviter: ownerId,
          inviterName: this.getPlayerName(ownerId),
          invitee: targetIds[0] || '',
          inviteeName: targetIds[0] ? this.getPlayerName(targetIds[0]) : '',
          createdAt
        };
        window.voiceChatEventBus.$emit('private-room-created', {
          roomId,
          participants: [this.currentPlayerId],
          createdAt,
          inviteInfo
        });
        window.voiceChatEventBus.$emit('private-room-sync', {
          roomId,
          participants: [this.currentPlayerId]
        });
      }

      // 发送创建消息到服务器/其他客户端
      const ok = this.sendVoiceChatMessage('voice_private_create_room', payload);
      if (ok) {
        this._showCenterBanner(`已向 ${targetIds.length} 人发送私聊邀请`);
      } else {
        // 回滚：退出房间并关闭本地房间
        try { this.leavePrivateVoiceRoom(roomId); } catch (e) { /* ignore */ }
        this.$store.commit('voiceChat/updateChatRoom', {
          type: 'private',
          roomId,
          data: { active: false, participants: [] }
        });
        this.showToast('error', '发送邀请失败，已回滚房间创建');
      }
    },

    // 被邀请者：回应邀请
    respondInvite(approved) {
      const invite = this.incomingInvite;
      if (!invite) return;
      const { roomId } = invite;
      // 关闭模态
      this.inviteModalState = { visible: false };
      this.$store.commit('voiceChat/setIncomingInvite', null);
      // 发送回应到房主端
      this.sendVoiceChatMessage('voice_private_invite_response', {
        roomId,
        inviteeId: this.currentPlayerId,
        approved: !!approved
      });
      if (approved) {
        this._showCenterBanner('已同意加入，等待房主确认与广播');
      }
    },

    // 房主：回应加入申请
    respondJoinApproval(approved) {
      const approval = this.joinApproval;
      if (!approval) return;
      const { roomId, requesterId } = approval;
      // 关闭模态
      this.joinApprovalState = { visible: false };
      this.$store.commit('voiceChat/setJoinApproval', null);
      // 发送审批结果
      this.sendVoiceChatMessage('voice_private_join_response', {
        roomId,
        requesterId,
        approved: !!approved
      });
      if (approved) {
        this._showCenterBanner('已同意加入，正在添加到房间');
      }
    },
    
    // 初始化真实语音引擎
    async initializeRealVoiceEngine() {
      this.$config.logger.debug('🎤 初始化真实语音引擎...');
      
      // 检查WebRTC支持
      if (!window.RTCPeerConnection) {
        this.$config.logger.error('当前浏览器不支持WebRTC');
        this.showToast('error', '浏览器不支持语音聊天功能');
        return false;
      }
      
      try {
        // 创建语音引擎实例
        this.voiceEngine = new VoiceEngine();
        
        // 设置当前玩家ID
        this.voiceEngine.setCurrentPlayerId(this.currentPlayerId);
        
        // 设置传统事件回调（向后兼容）
        this.voiceEngine.onSpeakingStateChanged = (isSpeaking) => {
          this.handleLocalSpeakingStateChanged(isSpeaking);
        };
        
        this.voiceEngine.onConnectionQualityChanged = (playerId, quality) => {
          this.handleConnectionQualityChanged(playerId, quality);
        };
        
        this.voiceEngine.onRemoteStreamReceived = (playerId, stream) => {
          this.handleRemoteStreamReceived(playerId, stream);
        };
        
        this.voiceEngine.onConnectionStateChanged = (playerId, state) => {
          this.handleConnectionStateChanged(playerId, state);
        };
        
        this.voiceEngine.onSignalingMessage = (toPlayerId, message) => {
          this.sendSignalingMessage(toPlayerId, message);
        };
        
        // 设置新的房间级别事件回调
        this.voiceEngine.onRoomStreamReceived = (roomId, playerId, stream) => {
          this.handleRoomStreamReceived(roomId, playerId, stream);
        };
        
        this.voiceEngine.onRoomConnectionStateChanged = (roomId, playerId, state) => {
          this.handleRoomConnectionStateChanged(roomId, playerId, state);
        };
        
        this.voiceEngine.onRoomJoined = (roomId) => {
          this.handleRoomJoined(roomId);
        };
        
        this.voiceEngine.onRoomLeft = (roomId) => {
          this.handleRoomLeft(roomId);
        };
        
        this.$config.logger.debug('✅ 真实语音引擎初始化完成（支持房间级别音频隔离）');
        return true;
        
      } catch (error) {
        this.$config.logger.error('真实语音引擎初始化失败:', error);
        this.showToast('error', '语音引擎初始化失败: ' + error.message);
        return false;
      }
    },
    
    // 麦克风控制
    async toggleMicrophone() {
      if (this.isMuted) return;
      
      const newState = !this.microphoneEnabled;
      
      if (newState) {
        // 启用麦克风：初始化语音引擎
        if (!this.voiceEngine) {
          const success = await this.initializeRealVoiceEngine();
          if (!success) {
            this.showToast('error', '麦克风初始化失败');
            return;
          }
        }
        
        // 启用麦克风
        this.voiceEngine.setMicrophoneEnabled(true);
        this.microphoneEnabled = true;
        this.showToast('success', '麦克风已启用');
        
      } else {
        // 禁用麦克风
        if (this.voiceEngine) {
          this.voiceEngine.setMicrophoneEnabled(false);
        }
        this.microphoneEnabled = false;
        this.showToast('info', '麦克风已禁用');
      }
    },
    
    // 公共聊天控制
    async joinPublicChat(isRetry = false) {
      // 在任意私聊中时，禁止操作公聊（玩家和说书人一致）
      if (this.isInAnyPrivateChat) {
        this.showToast('warning', '已在私聊中，无法加入公共聊天');
        return;
      }
      this.$config.logger.debug('🚀 joinPublicChat函数开始执行', isRetry ? '(重试)' : '');
      this.$config.logger.debug('🔍 函数入口WebSocket状态:', {
        socketExists: !!this.$store.state.socket._socket,
        socketReadyState: this.$store.state.socket._socket ? this.$store.state.socket._socket.readyState : 'N/A',
        sessionId: this.$store.state.session.sessionId,
        playerId: this.currentPlayerId
      });
      
      if (!this.canJoinPublicChat) return;
      
      // 检查WebSocket连接状态（仅在非重试时进行等待）
      const socket = this.$store.state.socket._socket;
      if (!socket || socket.readyState !== WebSocket.OPEN) {
        if (isRetry) {
          this.$config.logger.error('❌ 重试时WebSocket仍未连接');
          this.showToast('error', '连接服务器失败，请检查网络连接');
          return;
        }
        
        this.$config.logger.warn('⚠️ WebSocket未连接，尝试等待连接建立...');
        this.showToast('info', '正在连接服务器，请稍等...');
        
        // 等待WebSocket连接建立，最多等待5秒
        let attempts = 0;
        const maxAttempts = 10; // 5秒，每500ms检查一次
        
        const checkConnection = () => {
          attempts++;
          const currentSocket = this.$store.state.socket._socket;
          if (currentSocket && currentSocket.readyState === WebSocket.OPEN) {
            this.$config.logger.debug('✅ WebSocket连接已建立，继续加入公共聊天');
            // 递归调用自己，标记为重试
            this.joinPublicChat(true);
            return;
          }
          
          if (attempts >= maxAttempts) {
            this.$config.logger.error('❌ WebSocket连接超时');
            this.showToast('error', '连接服务器超时，请检查网络连接');
            return;
          }
          
          setTimeout(checkConnection, 500);
        };
        
        setTimeout(checkConnection, 500);
        return;
      }
      
      try {
        // 确保语音引擎已初始化
        if (!this.voiceEngine) {
          const success = await this.initializeRealVoiceEngine();
          if (!success) {
            this.showToast('error', '语音引擎初始化失败，无法加入公共聊天');
            return;
          }
        }
        
        // 更新本地状态
        const newParticipants = [...this.chatRooms.public.participants];
        if (!newParticipants.includes(this.currentPlayerId)) {
          newParticipants.push(this.currentPlayerId);
        }
        
        this.$config.logger.debug('🎤 当前完整会话状态:', {
          sessionId: this.$store.state.session.sessionId,
          playerId: this.currentPlayerId,
          isSpectator: this.$store.state.session.isSpectator,
          urlHash: window.location.hash,
          urlHref: window.location.href
        });
        this.$config.logger.debug('🎤 玩家加入公共聊天 - 更新状态:', {
          currentParticipants: this.chatRooms.public.participants,
          newParticipants: newParticipants,
          playerId: this.currentPlayerId
        });
        
        this.$store.commit('voiceChat/updateChatRoom', {
          type: 'public',
          data: {
            active: true,
            participants: newParticipants
          }
        });
        

        
        // 与现有参与者建立P2P连接（使用更新前的参与者列表）
        const existingParticipants = this.chatRooms.public.participants.filter(
          id => id !== this.currentPlayerId
        );
        
        for (const participantId of existingParticipants) {
          this.$config.logger.debug(`🔗 建立与 ${this.getPlayerName(participantId)} 的P2P连接`);
          await this.voiceEngine.createPeerConnection(participantId, true);
        }
        
        // 详细检查WebSocket连接状态
        const socket = this.$store.state.socket._socket;
        this.$config.logger.debug('🔍 WebSocket连接状态检查:', {
          socketExists: !!socket,
          socketReadyState: socket ? socket.readyState : 'N/A',
          socketUrl: socket ? socket.url : 'N/A',
          isSpectator: this.$store.state.session.isSpectator,
          playerId: this.currentPlayerId
        });
        
        // 通过WebSocket通知其他人
        this.$config.logger.debug('🌐 发送WebSocket消息 - voice_chat_join:', {
          type: 'public',
          playerId: this.currentPlayerId,
          playerName: this.getPlayerName(this.currentPlayerId),
          sessionId: this.$store.state.session.sessionId,
          action: 'join'
        });
        
        // 计算是否入座与显示名
        const isSeated = !!this.players.find(p => p.id === this.currentPlayerId);
        const isStorytellerClient = !this.$store.state.session.isSpectator;
        const selfPlayer = this.players.find(p => p.id === this.currentPlayerId);
        const normalizedDisplayName = isStorytellerClient
          ? this.getStorytellerDisplayName()
          : (selfPlayer?.name || '【访客】');
        
        const success = this.sendVoiceChatMessage('voice_chat_join', {
          type: 'public',
          playerId: this.currentPlayerId,
          playerName: this.getPlayerName(this.currentPlayerId),
          // 附带是否入座、是否说书人与建议显示名，方便服务端/其他客户端同步
          isSeated: isSeated,
          isStoryteller: isStorytellerClient,
          displayName: normalizedDisplayName,
          sessionId: this.$store.state.session.sessionId,
          action: 'join'
        });
        
        if (success) {
          this.$config.logger.debug('✅ WebSocket消息已发送');
        } else {
          this.$config.logger.error('❌ WebSocket消息发送失败');
          this.$config.logger.debug('💡 等待WebSocket连接建立...');
          
          // 尝试等待WebSocket连接建立
          let retryCount = 0;
          const maxRetries = 5;
          
          while (retryCount < maxRetries) {
            await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
            retryCount++;
            
            const currentSocket = this.$store.state.socket._socket;
            this.$config.logger.debug(`🔄 第${retryCount}次检查WebSocket连接:`, {
              socketExists: !!currentSocket,
              socketReadyState: currentSocket ? currentSocket.readyState : 'N/A'
            });
            
            if (currentSocket && currentSocket.readyState === 1) {
              this.$config.logger.debug('✅ WebSocket连接已建立，重新尝试加入语音聊天');
              return this.joinPublicChat(); // 重新调用
            }
          }
          
          this.$config.logger.error('❌ WebSocket连接等待超时');
          this.$config.logger.debug('💡 尝试手动触发WebSocket连接...');
          
          // 尝试从playerId恢复sessionId
          const playerIdPartsForRecovery = this.currentPlayerId.split('_');
          const recoveredSessionId = playerIdPartsForRecovery[0];
          
          if (recoveredSessionId && /^\d+$/.test(recoveredSessionId)) {
            this.$config.logger.debug('🔧 从playerId恢复sessionId:', recoveredSessionId);
            this.$config.logger.debug('🔧 手动触发WebSocket连接...');
            
            // 先恢复URL hash
            window.history.pushState(null, "", window.location.href.split("#")[0] + "#" + recoveredSessionId);
            this.$config.logger.debug('🔧 恢复URL hash:', window.location.href);
            
            // 然后设置sessionId
            this.$store.commit('session/setSessionId', recoveredSessionId);
            
            // 再等待2秒看看是否连接成功
            await new Promise(resolve => setTimeout(resolve, 2000));
            
            const finalSocket = this.$store.state.socket._socket;
            this.$config.logger.debug('🔧 恢复后的连接状态:', {
              sessionId: this.$store.state.session.sessionId,
              socketExists: !!finalSocket,
              socketReadyState: finalSocket ? finalSocket.readyState : 'N/A',
              urlHash: window.location.hash
            });
            
            if (finalSocket && finalSocket.readyState === 1) {
              this.$config.logger.debug('✅ 手动触发连接成功，重新尝试加入语音聊天');
              return this.joinPublicChat();
            } else {
              this.$config.logger.error('❌ 手动触发连接也失败了');
            }
          } else {
            this.$config.logger.error('❌ 无法从playerId中恢复有效的sessionId');
          }
          
          this.$config.logger.debug('💡 提示：请检查网络连接或刷新页面重试');
          
          // 检查是否有sessionId
          this.$config.logger.debug('🔍 检查sessionId状态:', {
            sessionId: this.$store.state.session.sessionId,
            session: this.$store.state.session,
            hasSessionId: !!this.$store.state.session.sessionId,
            playerId: this.currentPlayerId,
            windowHash: window.location.hash,
            windowHref: window.location.href
          });
          
          // 尝试从playerId中提取sessionId
          const playerIdParts = this.currentPlayerId.split('_');
          const possibleSessionId = playerIdParts[0];
          this.$config.logger.debug('🔍 从playerId分析:', {
            playerId: this.currentPlayerId,
            playerIdParts: playerIdParts,
            possibleSessionId: possibleSessionId,
            isNumeric: /^\d+$/.test(possibleSessionId)
          });
          
          if (!this.$store.state.session.sessionId) {
            this.$config.logger.error('❌ 没有有效的游戏会话ID，请先创建或加入游戏');
            this.$config.logger.debug('💡 提示：请确保您已经通过正确的链接加入游戏，或者是说书人已创建了房间');
            this.$config.logger.debug('🔍 当前URL hash:', window.location.hash);
            this.$config.logger.debug('🔍 建议：如果您是说书人且URL包含房间ID，请刷新页面重试');
            this.showToast('error', '请先创建或加入游戏会话');
            return;
          } else {
            this.$config.logger.debug('📍 当前sessionId存在:', this.$store.state.session.sessionId);
            this.$config.logger.error('❌ WebSocket连接不存在，但sessionId有效');
            this.$config.logger.debug('💡 这通常是临时的连接问题，请稍后重试');
            this.showToast('error', '连接断开，请稍后重试');
            return;
          }
        }
        
        this.showToast('success', '已加入公共语音聊天');
        
        // 使用新的房间音频API加入公聊房间
        this.joinPublicVoiceRoom();
        
      } catch (error) {
        this.$config.logger.error('加入公共聊天失败:', error);
        this.showToast('error', '加入公共聊天失败: ' + error.message);
      }
    },
    
    leavePublicChat() {
      // 在任意私聊中时，禁止操作公聊（玩家和说书人一致）
      if (this.isInAnyPrivateChat) {
        this.showToast('warning', '已在私聊中，无法操作公共聊天');
        return;
      }
      const currentParticipants = [...this.chatRooms.public.participants];
      const newParticipants = currentParticipants.filter(id => id !== this.currentPlayerId);
      
      // 关闭与公共聊天参与者的P2P连接
      if (this.voiceEngine) {
        currentParticipants.forEach(participantId => {
          if (participantId !== this.currentPlayerId) {
            this.voiceEngine.closePeerConnection(participantId);
          }
        });
      }
      
      // 更新本地状态
      this.$store.commit('voiceChat/updateChatRoom', {
        type: 'public',
        data: {
          participants: newParticipants,
          active: newParticipants.length > 0
        }
      });
      
      // 通过WebSocket同步到其他客户端
      this.sendVoiceChatMessage('voice_chat_leave', {
        type: 'public',
        playerId: this.currentPlayerId,
        playerName: this.getPlayerName(this.currentPlayerId),
        sessionId: this.$store.state.session.sessionId,
        action: 'leave'
      });
      
      this.showToast('info', '已离开公共语音聊天');
      
      // 使用新的房间音频API离开公聊房间
      this.leavePublicVoiceRoom();
    },
    
    // 私人聊天控制
    sendPrivateChatRequest(targetPlayerId) {
      if (this.testMode) {
        // 测试模式：模拟发送申请并同步
        const request = {
          id: `request_${Date.now()}`,
          fromId: this.currentPlayerId,
          toId: targetPlayerId,
          timestamp: Date.now(),
          status: 'pending'
        };
        
        // 更新本地状态
        this.$store.commit('voiceChat/addPrivateChatRequest', request);
        
        // 通过WebSocket同步到目标玩家
        this.sendVoiceChatMessage('voice_private_request', {
          request,
          fromName: this.getPlayerName(this.currentPlayerId),
          toName: this.getPlayerName(targetPlayerId)
        });
        
        this.showToast('info', `已向 ${this.getPlayerName(targetPlayerId)} 发送私聊申请`);
      } else {
        this.requestPrivateChat({ targetPlayerId });
        this.showToast('info', `已向 ${this.getPlayerName(targetPlayerId)} 发送私聊申请`);
      }
    },
    
    // 说书人直接建立私聊（无需申请）
    createDirectPrivateChat(targetPlayerId) {
      this.$config.logger.debug('🎭 说书人直接建立私聊:', targetPlayerId);
      this.$config.logger.debug('🔍 当前说书人ID:', this.currentPlayerId);
      this.$config.logger.debug('🔍 当前所有私聊房间:', this.chatRooms.private);
      
      // 首先检查说书人自己和目标玩家是否在其他私聊房间中
      const currentPlayerRooms = []; // 说书人自己的房间
      const targetPlayerCurrentRooms = []; // 目标玩家的房间
      
      if (this.chatRooms.private && this.chatRooms.private instanceof Map) {
        this.chatRooms.private.forEach((room, roomId) => {
          if (room.active && 
              room.participants && 
              Array.isArray(room.participants)) {
            
            // 检查说书人是否在这个房间
            if (room.participants.includes(this.currentPlayerId)) {
              currentPlayerRooms.push({ roomId, participants: room.participants });
            }
            
            // 检查目标玩家是否在这个房间
            if (room.participants.includes(targetPlayerId)) {
              targetPlayerCurrentRooms.push({ roomId, participants: room.participants });
            }
          }
        });
      }
      
      this.$config.logger.debug('🔍 说书人当前所在的房间:', currentPlayerRooms);
      this.$config.logger.debug('🔍 目标玩家当前所在的房间:', targetPlayerCurrentRooms);
      
      // 先处理说书人自己的房间（离开所有当前房间）
      currentPlayerRooms.forEach(({ roomId, participants }) => {
        const remainingParticipants = participants.filter(id => id !== this.currentPlayerId);
        
        this.$config.logger.debug(`📤 说书人离开房间 ${roomId}，剩余参与者:`, remainingParticipants);
        
        if (remainingParticipants.length === 0) {
          // 房间空了，关闭房间
          this.$store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: { active: false, participants: [] }
          });
          
          // 广播房间关闭
          this.sendVoiceChatMessage('voice_private_room_update', {
            action: 'closed',
            roomId,
            reason: 'empty'
          });
        } else {
          // 更新房间参与者
          this.$store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: { participants: remainingParticipants }
          });
          
          // 广播房间参与者变化，触发单人房间检查
          this.sendVoiceChatMessage('voice_private_room_update', {
            action: 'participant_removed',
            roomId,
            removedPlayerId: this.currentPlayerId,
            remainingParticipants: remainingParticipants
          });
        }
      });
      
      // 再处理目标玩家的房间（移除目标玩家）
      targetPlayerCurrentRooms.forEach(({ roomId, participants }) => {
        const remainingParticipants = participants.filter(id => id !== targetPlayerId);
        
        this.$config.logger.debug(`📤 从房间 ${roomId} 移除玩家 ${targetPlayerId}，剩余参与者:`, remainingParticipants);
        
        if (remainingParticipants.length === 0) {
          // 房间空了，关闭房间
          this.$store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: { active: false, participants: [] }
          });
          
          // 广播房间关闭
          this.sendVoiceChatMessage('voice_private_room_update', {
            action: 'closed',
            roomId,
            reason: 'empty'
          });
        } else {
          // 更新房间参与者
          this.$store.commit('voiceChat/updateChatRoom', {
            type: 'private',
            roomId,
            data: { participants: remainingParticipants }
          });
          
          // 广播房间参与者变化，触发单人房间检查
          this.sendVoiceChatMessage('voice_private_room_update', {
            action: 'participant_removed',
            roomId,
            removedPlayerId: targetPlayerId,
            remainingParticipants: remainingParticipants
          });
        }
      });
      
      // 延迟一下确保所有房间移除操作完成
      setTimeout(() => {
        // 生成新房间ID（统一使用排序后的格式）
        const roomId = [this.currentPlayerId, targetPlayerId].sort().join('-');
        this.$config.logger.debug('🆔 生成新房间ID:', roomId);
      
      // 直接创建私聊房间
        this.$config.logger.debug('📝 提交房间创建到Vuex store...');
      this.$store.commit('voiceChat/updateChatRoom', {
        type: 'private',
        roomId,
        data: {
          active: true,
          participants: [this.currentPlayerId, targetPlayerId],
          muted: false,
          createdAt: Date.now()
        }
      });
        
        // 立即检查store状态
        this.$config.logger.debug('✅ Vuex store状态更新后:', this.chatRooms.private.get(roomId));
        
        // 记录房间邀请信息
        this.$config.logger.debug('🔍 调试信息:');
        this.$config.logger.debug('  - currentPlayerId:', this.currentPlayerId);
        this.$config.logger.debug('  - session.sessionId:', this.$store.state.session?.sessionId);
        this.$config.logger.debug('  - currentUserRole:', this.$store.state.voiceChat.users?.get(this.currentPlayerId)?.role);
        this.$config.logger.debug('  - targetPlayerId:', targetPlayerId);
        
        const inviteInfo = {
          inviter: this.currentPlayerId,
          invitee: targetPlayerId,
          inviterName: this.getPlayerName(this.currentPlayerId),
          inviteeName: this.getPlayerName(targetPlayerId),
          createdAt: Date.now()
        };
        this.roomInviteInfo.set(roomId, inviteInfo);
        this.$config.logger.debug('📝 记录房间邀请信息:', inviteInfo);
      
      // 广播私聊房间创建给所有玩家
        this.$config.logger.debug('📢 广播房间创建事件...');
      this.sendVoiceChatMessage('voice_private_room_update', {
        action: 'created',
        roomId,
        participants: [this.currentPlayerId, targetPlayerId],
          createdAt: Date.now(),
          inviteInfo: inviteInfo // 包含邀请信息
        });
        
        // 强制刷新UI
        this.$config.logger.debug('🔄 强制刷新UI状态...');
        this.clearPrivateChatPlayersCache();
        this.expandedRoomsCount++;
        this.$forceUpdate();
        
        this.$config.logger.debug('🔍 创建完成后的所有房间:', this.chatRooms.private);
        this.$config.logger.debug('🔍 allActivePrivateChats:', this.allActivePrivateChats);
      
      this.showToast('success', `已与 ${this.getPlayerName(targetPlayerId)} 建立私聊`);
      }, 100); // 延迟100ms
    },
    
    handleRequest(requestId, approved) {
      this.$config.logger.debug('🔄 开始处理私聊申请响应');
      this.$config.logger.debug('待处理申请列表:', this.pendingPrivateChatRequests);
      this.$config.logger.debug('目标申请ID:', requestId);
      this.$config.logger.debug('用户操作:', approved ? '同意' : '拒绝');
      
      const request = this.pendingPrivateChatRequests.find(r => r.id === requestId);
      if (!request) {
        this.$config.logger.error('❌ 找不到私聊申请:', requestId);
        this.$config.logger.debug('所有私聊申请:', this.pendingPrivateChatRequests);
        return;
      }
      
      this.$config.logger.debug('✅ 找到申请记录:', request);
      this.$config.logger.debug('🔄 处理私聊申请:', { requestId, approved, request });
      
      // 更新本地状态
      this.$store.commit('voiceChat/updatePrivateChatRequest', {
        requestId,
        status: approved ? 'approved' : 'rejected'
      });
      
      if (approved) {
        // 创建私人聊天房间
        const roomId = [request.fromId, request.toId].sort().join('-');
        this.$store.commit('voiceChat/updateChatRoom', {
          type: 'private',
          roomId,
          data: {
            active: true,
            participants: [request.fromId, request.toId],
            muted: false,
            createdAt: Date.now()
          }
        });
        this.$config.logger.debug('创建私人聊天房间:', roomId);
        
        // 记录房间邀请信息
        const inviteInfo = {
          inviter: request.fromId,
          invitee: request.toId,
          inviterName: this.getPlayerName(request.fromId),
          inviteeName: this.getPlayerName(request.toId),
          createdAt: Date.now()
        };
        this.roomInviteInfo.set(roomId, inviteInfo);
        this.$config.logger.debug('📝 记录房间邀请信息:', inviteInfo);
        
        // 立即强制刷新私聊数据，确保房间信息能立即显示
        this.clearPrivateChatPlayersCache();
        this.expandedRoomsCount++;
        this.$forceUpdate();
        
        // 如果当前在私人聊天页签，确保信息立即显示
        if (this.activeMainTab === 'private') {
          this.$nextTick(() => {
            this.$config.logger.debug('🔄 私聊申请通过，当前在私人页签，确保立即显示');
            this.clearPrivateChatPlayersCache();
            this.expandedRoomsCount++;
            this.$forceUpdate();
          });
        }
      }
      
      // 通过WebSocket同步结果给发送申请的用户
      const responseMessage = {
        requestId,
        approved,
        fromId: request.fromId,
        toId: request.toId,
        roomId: approved ? [request.fromId, request.toId].sort().join('-') : null
      };
      
      this.$config.logger.debug('📤 准备发送私聊响应消息:', responseMessage);
      
      const success = this.sendVoiceChatMessage('voice_private_response', responseMessage);
      
      if (success) {
        this.$config.logger.debug('✅ 私聊响应消息发送成功');
      } else {
        this.$config.logger.error('❌ 私聊响应消息发送失败');
      }
      
      // 如果同意私聊，广播房间状态给所有玩家
      if (approved && responseMessage.roomId) {
        this.$config.logger.debug('📢 广播私聊房间创建给所有玩家');
        this.sendVoiceChatMessage('voice_private_room_update', {
          action: 'created',
          roomId: responseMessage.roomId,
          participants: [request.fromId, request.toId],
          createdAt: Date.now()
        });
      }
      
      this.showToast('info', approved ? '已同意私聊申请' : '已拒绝私聊申请');
    },
    
    leavePrivateChat(roomId) {
      const room = this.chatRooms.private.get(roomId);
      if (!room) return;
      
      // 更新本地状态
      this.$store.commit('voiceChat/updateChatRoom', {
        type: 'private',
        roomId,
        data: { 
          active: false,
          participants: room.participants.filter(id => id !== this.currentPlayerId)
        }
      });
      
      // 清除缓存，确保UI立即更新
      this.clearPrivateChatPlayersCache();
      
      // 通过WebSocket同步
      this.sendVoiceChatMessage('voice_private_leave', {
        roomId,
        playerId: this.currentPlayerId,
        playerName: this.getPlayerName(this.currentPlayerId)
      });
      
      this.showToast('info', '已离开私人聊天');
      
      // 使用新的房间音频API离开私聊房间
      this.leavePrivateVoiceRoom(roomId);
    },
    
    // 从私聊回到公聊
    returnToPublicChat(roomId) {
      this.$config.logger.debug('🔄 从私聊回到公聊:', roomId);
      
      // 先离开私聊
      this.leavePrivateChat(roomId);
      
      // 然后尝试加入公聊（如果未加入的话）
      if (!this.isInPublicChat && this.canJoinPublicChat) {
        this.$config.logger.debug('🔄 自动加入公聊');
        this.joinPublicChat();
      } else if (this.isInPublicChat) {
        this.showToast('info', '已回到公共聊天');
      } else {
        this.showToast('warning', '无法加入公共聊天，请检查权限或网络连接');
      }
    },
    
    // 私聊参与者列表折叠管理
    toggleParticipantsList(roomId) {
      const newState = !this.expandedParticipantsLists[roomId];
      // 直接使用 Vue.set 设置状态
      this.$set(this.expandedParticipantsLists, roomId, newState);
    },
    
    isParticipantsListExpanded(roomId) {
      // 添加计数器依赖确保响应式更新
      this.expandedRoomsCount; // 访问计数器触发响应式
      return !!this.expandedParticipantsLists[roomId];
    },
    
    // 说书人控制
    toggleStorytellerPanel() {
      this.$store.commit('voiceChat/toggleStorytellerPanel', !this.storytellerPanelVisible);
    },
    
    // 状态检查
    isSpeaking(playerId) {
      return this.speakingUsers.has(playerId);
    },
    
    // 有效禁言：全局禁言或玩家被单独禁言
    isPlayerMuted(playerId) {
      return this.globalMute || !!(this.playerPermissions[playerId]?.isMuted);
    },
    
    getSignalStrength(playerId) {
      // 优先使用语音引擎的真实连接质量数据
      if (this.voiceEngine) {
        const realQuality = this.voiceEngine.getConnectionQuality(playerId);
        if (realQuality.quality !== 'unknown') {
          return realQuality.quality;
        }
      }
      
      // 使用缓存的质量数据
      const cached = this.connectionQualities.get(playerId);
      if (cached && Date.now() - cached.timestamp < 8000) { // 8秒缓存
        return cached.quality;
      }
      
      // 基于玩家状态的动态信号质量（兜底逻辑）
      const isMuted = this.isPlayerMuted(playerId);
      const isSpeaking = this.isSpeaking(playerId);
      
      let baseQuality = 0.7; // 基础质量
      
      // 被禁音的玩家信号质量下降
      if (isMuted) {
        baseQuality -= 0.2;
      }
      
      // 正在说话的玩家信号质量稍微提升
      if (isSpeaking) {
        baseQuality += 0.1;
      }
      
      // 添加随机波动
      const randomFactor = (Math.random() - 0.5) * 0.3;
      const finalQuality = Math.max(0, Math.min(1, baseQuality + randomFactor));
      
      let quality;
      if (finalQuality >= 0.8) {
        quality = 'excellent';
      } else if (finalQuality >= 0.5) {
        quality = 'good';
      } else {
        quality = 'poor';
      }
      
      // 缓存结果
      this.connectionQualities.set(playerId, {
        quality,
        timestamp: Date.now(),
        numericValue: finalQuality
      });
      
      return quality;
    },
    
    // 获取连接延迟（毫秒）
    getConnectionPing(playerId) {
      // 优先使用语音引擎的真实延迟数据
      if (this.voiceEngine) {
        const realQuality = this.voiceEngine.getConnectionQuality(playerId);
        if (realQuality.rtt > 0) {
          return realQuality.rtt;
        }
      }
      
      // 使用缓存数据
      const cached = this.connectionQualities.get(playerId);
      if (cached && cached.ping && Date.now() - cached.timestamp < 10000) {
        return cached.ping;
      }
      
      // 兜底逻辑：基于信号质量估算延迟
      const quality = this.getSignalStrength(playerId);
      let basePing;
      
      switch (quality) {
        case 'excellent':
          basePing = 20 + Math.random() * 30; // 20-50ms
          break;
        case 'good':
          basePing = 50 + Math.random() * 100; // 50-150ms
          break;
        case 'poor':
          basePing = 150 + Math.random() * 350; // 150-500ms
          break;
        default:
          basePing = 100;
      }
      
      const ping = Math.round(basePing);
      
      // 更新缓存
      const existing = this.connectionQualities.get(playerId) || {};
      this.connectionQualities.set(playerId, {
        ...existing,
        ping,
        timestamp: Date.now()
      });
      
      return ping;
    },
    
    getConnectionStatusText(status) {
      const statusTexts = {
        excellent: '连接优秀',
        good: '连接良好', 
        poor: '连接较差',
        failed: '连接失败'
      };
      return statusTexts[status] || '未知状态';
    },
    
    // 移动端适配
    toggleExpand() {
      this.isExpanded = !this.isExpanded;
    },
    
    // 桌面端收起功能
    toggleMinimize() {
      this.isMinimized = !this.isMinimized;
    },
    
    checkMobile() {
      this.isMobile = window.innerWidth <= 768;
    },
    
    // 私聊展示相关方法
    isUserInPrivateChat(chat) {
      return chat.participants.includes(this.currentPlayerId);
    },
    
    getPrivateChatStatus(roomId) {
      // 基于房间中玩家的连接质量计算状态
      const room = this.chatRooms.private.get(roomId);
      if (!room || !room.participants) return 'poor';
      
      const qualities = room.participants.map(id => this.getSignalStrength(id));
      
      if (qualities.every(q => q === 'excellent')) return 'excellent';
      if (qualities.some(q => q === 'poor')) return 'poor';
      return 'good';
    },
    
    getChatStatusText(roomId) {
      const status = this.getPrivateChatStatus(roomId);
      const statusTexts = {
        excellent: '连接状态：优秀',
        good: '连接状态：良好',
        poor: '连接状态：较差'
      };
      return statusTexts[status] || '连接状态：未知';
    },
    
    getPrivateChatDuration(roomId) {
      const room = this.chatRooms.private.get(roomId);
      if (!room || !room.createdAt) return '';
      
      const duration = Date.now() - room.createdAt;
      const minutes = Math.floor(duration / 60000);
      const seconds = Math.floor((duration % 60000) / 1000);
      
      if (minutes > 0) {
        return `${minutes}:${seconds.toString().padStart(2, '0')}`;
      } else {
        return `${seconds}s`;
      }
    },
    
    getPrivateChatActivity(roomId) {
      const room = this.chatRooms.private.get(roomId);
      if (!room || !room.participants) return '';
      
      // 检查是否有人在说话
      const speakingParticipants = room.participants.filter(id => this.isSpeaking(id));
      
      if (speakingParticipants.length > 0) {
        return speakingParticipants.map(id => this.getPlayerName(id)).join(', ') + ' 正在说话';
      }
      
      return '';
    },
    
    getPrivateChatTitle(chat) {
      const otherParticipants = chat.participants.filter(id => id !== this.currentPlayerId);
      return otherParticipants.map(id => this.getPlayerName(id)).join(', ');
    },
    
    // 模拟语音活动检测（开发测试用）
    simulateVoiceActivity() {
      if (!this.testMode) return;
      
      this.voiceActivityTimer = setInterval(() => {
        // 获取所有活跃的参与者
        const publicParticipants = this.publicChatParticipants.map(p => p.id);
        const privateParticipants = this.activePrivateChats.flatMap(chat => chat.participants);
        const allParticipants = [...new Set([...publicParticipants, ...privateParticipants])];
        
        if (allParticipants.length === 0) return;
        
        // 过滤掉被禁音的玩家
        const activeParticipants = allParticipants.filter(playerId => {
          return !this.isPlayerMuted(playerId);
        });
        
        if (activeParticipants.length === 0) return;
        
        // 更真实的说话模拟：
        // 1. 30%概率有人开始说话
        // 2. 已经在说话的人有50%概率继续说话
        // 3. 说话时长在2-8秒之间
        
        activeParticipants.forEach(playerId => {
          const isSpeaking = this.speakingUsers.has(playerId);
          
          if (isSpeaking) {
            // 已经在说话，50%概率继续
            if (Math.random() > 0.5) {
              this.speakingUsers.delete(playerId);
              // 通过WebSocket同步说话状态
              this.syncSpeakingState(playerId, false);
            }
          } else {
            // 没在说话，30%概率开始说话
            if (Math.random() > 0.7) {
              this.speakingUsers.add(playerId);
              // 通过WebSocket同步说话状态
              this.syncSpeakingState(playerId, true);
              
              // 随机说话时长：2-8秒
              const speakingDuration = 2000 + Math.random() * 6000;
              setTimeout(() => {
                if (this.speakingUsers.has(playerId)) {
                  this.speakingUsers.delete(playerId);
                  this.syncSpeakingState(playerId, false);
                }
              }, speakingDuration);
            }
          }
        });
      }, 1500); // 更频繁的检测：1.5秒间隔
    },
    
    // 同步说话状态到其他客户端
    syncSpeakingState(playerId, isSpeaking) {
      this.sendVoiceChatMessage('voice_speaking_state', {
        playerId,
        isSpeaking,
        timestamp: Date.now(),
        playerName: this.getPlayerName(playerId)
      });
    },
    
    // 处理来自其他客户端的说话状态变化
    handleRemoteSpeakingState(data) {
      // 只处理不是自己发起的说话状态变化
      if (data.playerId !== this.currentPlayerId) {
        if (data.isSpeaking) {
          this.speakingUsers.add(data.playerId);
        } else {
          this.speakingUsers.delete(data.playerId);
        }
      }
    },
    
    // === 语音引擎事件处理 ===
    
    // 处理本地说话状态变化
    handleLocalSpeakingStateChanged(isSpeaking) {
      const wasLocalSpeaking = this.speakingUsers.has(this.currentPlayerId);
      
      if (isSpeaking && !wasLocalSpeaking) {
        this.speakingUsers.add(this.currentPlayerId);
        this.syncSpeakingState(this.currentPlayerId, true);
      } else if (!isSpeaking && wasLocalSpeaking) {
        this.speakingUsers.delete(this.currentPlayerId);
        this.syncSpeakingState(this.currentPlayerId, false);
      }
    },
    
    // 处理连接质量变化
    handleConnectionQualityChanged(playerId, qualityInfo) {
      this.connectionQualities.set(playerId, {
        ...qualityInfo,
        timestamp: Date.now()
      });
    },
    
    // 处理接收到远程音频流
    handleRemoteStreamReceived(playerId) {
      this.$config.logger.debug(`📻 接收到来自 ${this.getPlayerName(playerId)} 的音频流`);
      
      // 设置默认音量
      const defaultVolume = this.remoteVolumes.get(playerId) || 0.8;
      this.voiceEngine.setRemoteAudioVolume(playerId, defaultVolume);
      
      this.showToast('info', `${this.getPlayerName(playerId)} 已加入语音聊天`);
    },
    
    // 处理连接状态变化
    handleConnectionStateChanged(playerId, state) {
      // 连接状态日志已移除以减少控制台输出
      
      switch (state) {
        case 'connected':
          this.showToast('success', `与 ${this.getPlayerName(playerId)} 连接成功`);
          break;
        case 'disconnected':
        case 'failed':
          this.showToast('warning', `与 ${this.getPlayerName(playerId)} 连接断开`);
          break;
        case 'connecting':
          // 连接中的日志已移除
          break;
      }
    },
    
    // 发送信令消息
    sendSignalingMessage(toPlayerId, message) {
      this.sendVoiceChatMessage('voice_webrtc_signal', {
        fromPlayerId: this.currentPlayerId,
        toPlayerId,
        message
      });
    },
    
    // 处理接收到的信令消息
    handleSignalingMessage(fromPlayerId, message) {
      if (this.voiceEngine) {
        this.voiceEngine.handleSignalingMessage(fromPlayerId, message);
      }
    },
    
    // ================================
    // 房间级别音频事件处理方法
    // ================================
    
    // 处理房间中接收到远程音频流
    handleRoomStreamReceived(roomId, playerId, stream) {
      this.$config.logger.debug(`📻 在房间 ${roomId} 中接收到来自 ${this.getPlayerName(playerId)} 的音频流`);
      
      // 设置默认音量
      const defaultVolume = this.remoteVolumes.get(playerId) || 0.8;
      this.voiceEngine.setRemoteAudioVolume(playerId, defaultVolume);
      
      // 显示房间特定的提示
      const roomType = roomId.startsWith('private_') ? '私聊' : '公聊';
      this.showToast('info', `${this.getPlayerName(playerId)} 已加入${roomType}语音`);
      
      // stream参数在这里主要用于日志记录，实际音频处理由VoiceEngine内部完成
      this.$config.logger.debug(`🔊 音频流详情:`, { streamId: stream.id, tracks: stream.getTracks().length });
    },
    
    // 处理房间连接状态变化
    handleRoomConnectionStateChanged(roomId, playerId, state) {
      // 房间连接状态日志已移除以减少控制台输出
      
      switch (state) {
        case 'connected':
          // 连接成功日志已移除
          break;
        case 'disconnected':
        case 'failed':
        case 'closed':
          this.$config.logger.debug(`❌ 与 ${this.getPlayerName(playerId)} 在房间 ${roomId} 中连接断开`);
          this.showToast('warning', `与 ${this.getPlayerName(playerId)} 的连接已断开`);
          break;
      }
    },
    
    // 处理加入房间事件
    handleRoomJoined(roomId) {
      this.$config.logger.debug(`🚪 成功加入房间 ${roomId}`);
      const roomType = roomId.startsWith('private_') ? '私聊房间' : '公聊房间';
      this.showToast('success', `已加入${roomType}`);
      
      // 清除缓存，确保UI状态正确
      this.clearPrivateChatPlayersCache();
    },
    
    // 处理离开房间事件
    handleRoomLeft(roomId) {
      this.$config.logger.debug(`🚪 已离开房间 ${roomId}`);
      const roomType = roomId.startsWith('private_') ? '私聊房间' : '公聊房间';
      this.showToast('info', `已离开${roomType}`);
      
      // 清除缓存，确保UI状态正确
      this.clearPrivateChatPlayersCache();
    },
    
    // ================================
    // 房间级别音频管理方法
    // ================================
    
    /**
     * 加入公聊语音房间
     */
    async joinPublicVoiceRoom() {
      if (!this.voiceEngine) {
        this.$config.logger.warn('⚠️ 语音引擎未初始化，无法加入公聊语音房间');
        return;
      }
      
      try {
        const roomId = 'public';
        const participants = this.chatRooms.public.participants || [];
        
        this.$config.logger.debug(`🚪 加入公聊语音房间，参与者:`, participants);
        
        await this.voiceEngine.joinVoiceRoom(roomId, participants, 'public');
        
        this.$config.logger.debug(`✅ 成功加入公聊语音房间`);
        
      } catch (error) {
        this.$config.logger.error('❌ 加入公聊语音房间失败:', error);
        this.showToast('error', '语音连接失败: ' + error.message);
      }
    },
    
    /**
     * 离开公聊语音房间
     */
    async leavePublicVoiceRoom() {
      if (!this.voiceEngine) {
        return;
      }
      
      try {
        await this.voiceEngine.leaveVoiceRoom('public');
        this.$config.logger.debug(`✅ 已离开公聊语音房间`);
      } catch (error) {
        this.$config.logger.error('❌ 离开公聊语音房间失败:', error);
      }
    },
    
    /**
     * 加入私聊语音房间
     * @param {string} roomId 私聊房间ID
     * @param {Array} participants 参与者列表
     */
    async joinPrivateVoiceRoom(roomId, participants) {
      if (!this.voiceEngine) {
        this.$config.logger.warn('⚠️ 语音引擎未初始化，无法加入私聊语音房间');
        return;
      }
      
      try {
        // 过滤非法/占位ID，避免将 'host' 纳入参与者
        const filtered = (participants || []).filter(pid => pid && pid !== 'host');
        this.$config.logger.debug(`🔒 加入私聊语音房间 ${roomId}，参与者:`, filtered);
        
        // 先离开公聊语音房间（如果在其中）
        if (this.voiceEngine.getCurrentRooms().includes('public')) {
          await this.leavePublicVoiceRoom();
        }
        
        // 加入私聊语音房间（这会自动隔离音频）
        await this.voiceEngine.joinVoiceRoom(roomId, filtered, 'private');
        
        this.$config.logger.debug(`✅ 成功加入私聊语音房间 ${roomId}`);
        
      } catch (error) {
        this.$config.logger.error(`❌ 加入私聊语音房间 ${roomId} 失败:`, error);
        this.showToast('error', '私聊语音连接失败: ' + error.message);
      }
    },
    
    /**
     * 离开私聊语音房间
     * @param {string} roomId 私聊房间ID
     */
    async leavePrivateVoiceRoom(roomId) {
      if (!this.voiceEngine) {
        return;
      }
      
      try {
        await this.voiceEngine.leaveVoiceRoom(roomId);
        this.$config.logger.debug(`✅ 已离开私聊语音房间 ${roomId}`);
        
        // 自动重新加入公聊语音房间（如果仍在公聊中）
        if (this.isInPublicChat && this.publicChatEnabled) {
          await this.joinPublicVoiceRoom();
        }
        
      } catch (error) {
        this.$config.logger.error(`❌ 离开私聊语音房间 ${roomId} 失败:`, error);
      }
    },
    
    // 处理WebRTC信令事件
    handleWebRTCSignaling(data) {
      // 只处理发给自己的信令消息
      if (data.toPlayerId === this.currentPlayerId) {
        this.handleSignalingMessage(data.fromPlayerId, data.message);
      }
    },
    
    // 处理其他玩家加入公共聊天
    async handlePlayerJoinedPublicChat(data) {
      if (data.playerId === this.currentPlayerId) return;
      
      // 如果自己也在公共聊天中，需要与新加入的玩家建立连接
      const isInPublicChat = this.chatRooms.public.participants.includes(this.currentPlayerId);
      
      if (isInPublicChat && this.voiceEngine) {
        this.$config.logger.debug(`🔗 新玩家 ${data.playerName} 加入公共聊天，建立P2P连接`);
        try {
          // 作为接收方，不主动发起offer
          await this.voiceEngine.createPeerConnection(data.playerId, false);
        } catch (error) {
          this.$config.logger.error(`与 ${data.playerName} 建立连接失败:`, error);
        }
      }
    },
    
    // 处理语音聊天状态同步
    async handleVoiceChatStateSynced(data) {
      this.$config.logger.debug('🎯 收到语音聊天状态同步事件:', data);
      
      // 清除缓存，确保状态同步后UI正确更新
      this.clearPrivateChatPlayersCache();
      
      if (data.type === 'public' && data.participants) {
        this.$config.logger.debug('📋 同步公共聊天参与者列表:', data.participants);
        
        // 检查当前用户是否在参与者列表中
        const isCurrentUserInChat = data.participants.includes(this.currentPlayerId);
        
        // 如果当前用户也在聊天中，使用新的房间音频API更新连接
        if (isCurrentUserInChat && this.voiceEngine) {
          this.$config.logger.debug('🔄 检测到公聊参与者变化，更新房间音频连接');
          
          // 重新加入公聊语音房间以更新参与者连接
          await this.joinPublicVoiceRoom();
        }
        
        this.$config.logger.debug('✅ 公共语音聊天状态同步完成');
      }
      
      // 注意：私聊房间同步通过单独的 private-room-created 事件处理
      // 这里主要处理公共聊天的状态同步
    },
    
    // 处理私聊房间创建事件
    async handlePrivateRoomCreated(data) {
      this.$config.logger.debug('🔒 收到私聊房间创建事件:', data);
      
      // 同步邀请信息
      if (data.inviteInfo) {
        this.roomInviteInfo.set(data.roomId, data.inviteInfo);
        this.$config.logger.debug('📝 同步房间邀请信息:', data.inviteInfo);
      }
      
      // 多重强制刷新策略
      for (let i = 0; i < 3; i++) {
        // 立即清除缓存
        this.clearPrivateChatPlayersCache();
        
        // 强制重新计算
        this.getPlayersInPrivateChats();
        this.expandedRoomsCount++;
        this.$forceUpdate();
        
        // 等待Vue更新
        await this.$nextTick();
        
        // 检查是否成功
        const playersCount = this.getPlayersInPrivateChats().length;
        const roomsCount = this.allActivePrivateChats.length;
        
        this.$config.logger.debug(`🔄 私聊创建刷新第${i+1}次:`, {
          playersCount,
          roomsCount,
          success: (playersCount > 0 && roomsCount > 0)
        });
        
        // 如果成功同步，退出循环
        if (playersCount > 0 && roomsCount > 0) {
          this.$config.logger.debug('✅ 私聊房间数据同步成功');
          break;
        }
        
        // 如果还没成功，等待一段时间再重试
        if (i < 2) {
          await new Promise(resolve => setTimeout(resolve, 200));
        }
      }
      
      // 检查状态一致性
      this.checkInconsistencyState();
      
      // 无论当前在哪个页签，都强制刷新私聊数据，确保切换页签时能立即显示
      this.$config.logger.debug('🔄 强制刷新私聊数据，确保页签切换时立即显示');
        this.clearPrivateChatPlayersCache();
        this.expandedRoomsCount++;
        this.$forceUpdate();
      
      // 等待DOM更新后再次检查
      await this.$nextTick();
      
      // 检查当前用户是否是这个私聊房间的参与者
      if (data.participants && data.participants.includes(this.currentPlayerId)) {
        this.$config.logger.debug('✅ 当前用户是私聊房间参与者，准备加入语音聊天');
        // 如果当前仍在公聊，则先离开公聊并广播离开
        if (this.isInPublicChat) {
          try {
            await this.leavePublicChat();
          } catch (e) { /* 忽略离开公聊失败，继续尝试加入私聊 */ }
        }
        
        // 加入私聊语音房间
        await this.joinPrivateVoiceRoom(data.roomId, data.participants);
      } else {
        this.$config.logger.debug('ℹ️ 当前用户不是这个私聊房间的参与者，跳过语音连接');
      }
    },

    // 同步任意时刻的房间参与者变化（包括participant_added）
    async handlePrivateRoomSync(data) {
      try {
        if (!data || !data.roomId) return;
        const room = this.chatRooms.private.get(data.roomId);
        const participants = data.participants || room?.participants || [];
        // 只有当前用户在这个房间参与者里时才尝试加入
        if (participants.includes(this.currentPlayerId)) {
          await this.joinPrivateVoiceRoom(data.roomId, participants);
        }
      } catch (e) {
        this.$config.logger.error('同步私聊房间失败:', e);
      }
    },
    
    // === 音量控制 ===
    
    // 获取远程玩家音量
    getRemoteVolume(playerId) {
      return Math.round((this.remoteVolumes.get(playerId) || 0.8) * 100);
    },
    
    // 设置远程玩家音量
    setRemoteVolume(playerId, volume) {
      const normalizedVolume = Math.max(0, Math.min(100, parseInt(volume))) / 100;
      this.remoteVolumes.set(playerId, normalizedVolume);
      
      // 通过语音引擎设置实际音量
      if (this.voiceEngine) {
        this.voiceEngine.setRemoteAudioVolume(playerId, normalizedVolume);
      }
      
      this.$config.logger.debug(`🔊 设置 ${this.getPlayerName(playerId)} 音量为 ${Math.round(normalizedVolume * 100)}%`);
    },
    
    // 设置麦克风音量
    setMicrophoneVolume(volume) {
      this.microphoneVolume = Math.max(0, Math.min(1, volume));
      
      if (this.voiceEngine) {
        this.voiceEngine.setMicrophoneVolume(this.microphoneVolume);
      }
      
      this.$config.logger.debug(`🎤 设置麦克风音量为 ${Math.round(this.microphoneVolume * 100)}%`);
    },
    
    // 静音/取消静音远程玩家
    toggleRemotePlayerMute(playerId) {
      const currentVolume = this.remoteVolumes.get(playerId) || 0.8;
      const newVolume = currentVolume > 0 ? 0 : 0.8;
      
      this.setRemoteVolume(playerId, newVolume * 100);
      
      const playerName = this.getPlayerName(playerId);
      this.showToast('info', `${playerName} ${newVolume > 0 ? '已取消静音' : '已静音'}`);
    },
    
    // === 私聊监控扩展功能 ===
    
    // 静音私聊房间
    mutePrivateChat(roomId) {
      const room = this.chatRooms.private.get(roomId);
      if (!room) return;
      
      // 静音房间中的所有参与者
      room.participants.forEach(participantId => {
        if (participantId !== this.currentPlayerId) {
          this.setRemoteVolume(participantId, 0);
        }
      });
      
      const participants = room.participants.map(id => this.getPlayerName(id)).join(', ');
      this.showToast('info', `已静音私聊房间: ${participants}`);
    },
    
    // 显示私聊详细信息
    showChatInfo(roomId) {
      const room = this.chatRooms.private.get(roomId);
      if (!room) return;
      
      const duration = this.getPrivateChatDuration(roomId);
      const status = this.getPrivateChatStatus(roomId);
      
      // 这里可以显示一个更详细的信息模态框，目前使用简单的提示
      this.showToast('info', `房间 ${roomId.substring(0, 8)}... 详细信息已记录到控制台`);
      this.$config.logger.debug('🔍 私聊房间详细信息:', {
        roomId,
        participants: room.participants.map(id => ({ id, name: this.getPlayerName(id) })),
        duration,
        status,
        createdAt: room.createdAt ? new Date(room.createdAt).toLocaleString() : '未知',
        activity: this.getPrivateChatActivity(roomId)
      });
    },
    
    // 获取总参与人数
    getTotalParticipants() {
      const allParticipants = new Set();
      this.allActivePrivateChats.forEach(chat => {
        chat.participants.forEach(id => allParticipants.add(id));
      });
      return allParticipants.size;
    },
    
    // 获取平均时长
    getAverageDuration() {
      if (this.allActivePrivateChats.length === 0) return '0s';
      
      const totalDuration = this.allActivePrivateChats.reduce((sum, chat) => {
        if (chat.createdAt) {
          return sum + (Date.now() - chat.createdAt);
        }
        return sum;
      }, 0);
      
      const avgDuration = totalDuration / this.allActivePrivateChats.length;
      const minutes = Math.floor(avgDuration / 60000);
      const seconds = Math.floor((avgDuration % 60000) / 1000);
      
      if (minutes > 0) {
        return `${minutes}:${seconds.toString().padStart(2, '0')}`;
      } else {
        return `${seconds}s`;
      }
    },
    
    // 获取私聊连接状态文本
    getPrivateChatConnectionStatus(roomId) {
      const status = this.getPrivateChatStatus(roomId);
      switch (status) {
        case 'connected': return '🟢 已连接';
        case 'connecting': return '🟡 连接中';
        case 'disconnected': return '🔴 已断开';
        default: return '🟡 未知';
      }
    },
    
    // 获取参与者音量百分比
    getParticipantVolume(participantId) {
      // 简化实现，返回随机音量用于演示
      if (this.isSpeaking(participantId)) {
        return Math.floor(Math.random() * 60) + 40; // 40-100%
      }
      return Math.floor(Math.random() * 20) + 5; // 5-25%
    },
    
    // 获取所有在私聊中的玩家ID列表（带缓存优化）
    getPlayersInPrivateChats() {
      const now = Date.now();
      
      // 如果缓存还新鲜（1秒内），直接返回缓存
      if (now - this.lastCacheUpdate < 1000 && this.privateChatPlayersCache.length >= 0) {
        return this.privateChatPlayersCache;
      }
      
      const playersInPrivateChats = [];
      
      // 遍历所有私聊房间
      if (this.chatRooms.private && this.chatRooms.private instanceof Map) {
        this.chatRooms.private.forEach((room, roomId) => {
          this.$config.logger.debug('🔍 getPlayersInPrivateChats 检查房间:', roomId, room);
          // 更严格的检查：房间必须活跃，有足够参与者，且参与者数组有效
          if (room.active && 
              room.participants && 
              Array.isArray(room.participants) && 
              room.participants.length >= 2) {
            this.$config.logger.debug('🔍 房间符合条件，添加参与者:', room.participants);
            playersInPrivateChats.push(...room.participants);
          } else {
            this.$config.logger.debug('🔍 房间不符合条件:', {
              active: room.active,
              hasParticipants: !!room.participants,
              isArray: Array.isArray(room.participants),
              length: room.participants?.length
            });
          }
        });
      }
      
      const uniquePlayers = [...new Set(playersInPrivateChats)];
      this.$config.logger.debug('🔍 getPlayersInPrivateChats 最终结果:', uniquePlayers);
      
      // 更新缓存
      this.privateChatPlayersCache = uniquePlayers;
      this.lastCacheUpdate = now;
      
      return uniquePlayers;
    },
    
    // 检查玩家是否应该在公聊中显示
    shouldShowInPublicChat(playerId) {
      // 1. 玩家必须在公聊房间的参与者列表中
      if (!this.chatRooms.public.participants.includes(playerId)) {
        return false;
      }
      
      // 2. 玩家不能在任何活跃的私聊中
      const playersInPrivateChats = this.getPlayersInPrivateChats();
      if (playersInPrivateChats.includes(playerId)) {
        return false;
      }
      
      return true;
    },
    
    // 强制清除私聊玩家缓存
    clearPrivateChatPlayersCache() {
      this.privateChatPlayersCache = [];
      this.lastCacheUpdate = 0;
    },
    
    // 调试私聊状态不一致问题
    debugPrivateChatState() {
      this.$config.logger.debug('🔍🔍🔍 === 强制调试私聊状态 ===');
      this.$config.logger.debug('🔍 chatRooms.private:', this.chatRooms.private);
      
      // 强制清除缓存
      this.clearPrivateChatPlayersCache();
      
      // 强制触发计算属性重新计算
      this.expandedRoomsCount++;
      
      // 强制触发Vue的响应式更新
      this.$forceUpdate();
      
      // 等待下一个tick再检查
      this.$nextTick(() => {
        const players = this.getPlayersInPrivateChats();
        const rooms = this.allActivePrivateChats;
        
        this.$config.logger.debug('🔍 重新计算后:');
        this.$config.logger.debug('🔍 私聊玩家数量:', players.length, players);
        this.$config.logger.debug('🔍 活跃房间数量:', rooms.length, rooms);
      
        // 逐个检查房间
        if (this.chatRooms.private instanceof Map) {
          this.$config.logger.debug('🔍 === 逐个检查所有房间 ===');
          this.chatRooms.private.forEach((room, roomId) => {
            this.$config.logger.debug(`🔍 房间 ${roomId}:`);
            this.$config.logger.debug('  - active:', room.active);
            this.$config.logger.debug('  - participants:', room.participants);
            this.$config.logger.debug('  - isArray:', Array.isArray(room.participants));
            this.$config.logger.debug('  - length:', room.participants?.length);
            this.$config.logger.debug('  - 符合getPlayersInPrivateChats条件:', 
              room.active && room.participants && Array.isArray(room.participants) && room.participants.length >= 2);
            this.$config.logger.debug('  - 符合allActivePrivateChats条件:', 
              room.active && Array.isArray(room.participants) && room.participants.length >= 2);
          });
        }
      });
        },

    // 强制刷新所有聊天状态
    forceRefreshAllChats() {
      this.$config.logger.debug('🔄🔄🔄 === 强制刷新所有聊天状态 ===');
      
      // 1. 清除所有缓存
      this.clearPrivateChatPlayersCache();
      
      // 2. 强制触发所有响应式依赖
      this.expandedRoomsCount++;
      
      // 3. 强制重新计算所有计算属性
      this.$forceUpdate();
      
      // 4. 强制触发 Vuex getters 重新计算
      this.$store._vm.$forceUpdate();
      
      // 5. 等待DOM更新完成后验证
      this.$nextTick(() => {
        this.$config.logger.debug('🔄 刷新完成，验证状态:');
        
        // 强制重新获取数据
        const players = this.getPlayersInPrivateChats();
        const rooms = this.allActivePrivateChats;
        
        this.$config.logger.debug('🔄 私聊玩家:', players.length, players);
        this.$config.logger.debug('🔄 活跃房间:', rooms.length, rooms);
        
        // 检查chatRooms原始数据
        this.$config.logger.debug('🔄 原始chatRooms.private:', this.chatRooms.private);
        
        if (this.chatRooms.private instanceof Map) {
          this.$config.logger.debug('🔄 所有房间详情:');
          this.chatRooms.private.forEach((room, roomId) => {
            this.$config.logger.debug(`  ${roomId}:`, {
              active: room.active,
              participants: room.participants,
              length: room.participants?.length
            });
          });
        }
        
        // 如果还是不一致，尝试直接操作Vuex
        if (rooms.length === 0 && players.length > 0) {
          this.$config.logger.debug('🔄 状态仍然不一致，尝试Vuex层面修复...');
          // 可以在这里添加更激进的修复方法
        }
      });
    },

    // 切换监控区域展开状态
    toggleMonitorExpanded() {
      this.isMonitorExpanded = !this.isMonitorExpanded;
        },

    // 检查状态一致性
    checkInconsistencyState() {
      const hasInconsistency = this.allActivePrivateChats.length === 0 && this.getPlayersInPrivateChats().length > 0;
      
      if (hasInconsistency) {
        // 如果检测到不一致，开始计时
        if (!this.inconsistencyStartTime) {
          this.inconsistencyStartTime = Date.now();
          
          // 设置3秒后的检查定时器
          if (this.inconsistencyCheckTimer) {
            clearTimeout(this.inconsistencyCheckTimer);
          }
          
          this.inconsistencyCheckTimer = setTimeout(() => {
            this.$forceUpdate(); // 3秒后强制更新以显示警告框
          }, 3000);
        }
      } else {
        // 没有不一致，重置计时器
        if (this.inconsistencyStartTime) {
          this.inconsistencyStartTime = null;
          if (this.inconsistencyCheckTimer) {
            clearTimeout(this.inconsistencyCheckTimer);
            this.inconsistencyCheckTimer = null;
          }
        }
      }
    },

    // 检查是否有单人私聊房间需要提示
    checkAloneInRoom() {
      this.$config.logger.debug('🔍 检查单人房间状态...');
      
      // 遍历所有私聊房间
      if (this.chatRooms.private && this.chatRooms.private instanceof Map) {
        this.chatRooms.private.forEach((room, roomId) => {
          // 检查房间是否活跃且当前用户在其中
          if (room.active && 
              room.participants && 
              Array.isArray(room.participants) && 
              room.participants.includes(this.currentPlayerId)) {
            
            this.$config.logger.debug(`🔍 检查房间 ${roomId}:`, {
              participants: room.participants,
              participantCount: room.participants.length,
              currentUser: this.currentPlayerId
            });
            
            // 如果房间只有当前用户一个人
            if (room.participants.length === 1) {
              this.$config.logger.debug(`🚪 发现单人房间 ${roomId}，准备提示用户`);
              
              // 避免重复显示提示
              if (!this.showAloneRoomDialog) {
                this.aloneRoomId = roomId;
                this.showAloneRoomDialog = true;
                this.$config.logger.debug(`💬 显示单人房间提示对话框`);
              }
            }
          }
        });
      }
    },

    // 处理单人房间对话框的确认（现在只有确认按钮）
    handleAloneRoomChoice() {
      this.$config.logger.debug('✅ 用户确认了单人房间提示，已自动返回公聊');
      
      // 关闭对话框
      this.showAloneRoomDialog = false;
      this.aloneRoomId = null;
      this.aloneRoomInviteInfo = null;
    },

    // 处理来自事件总线的单人房间检查事件
    handleCheckAloneInRoom(data) {
      this.$config.logger.debug('📨 收到单人房间检查事件:', data);
      
      // 检查是否是当前用户的房间
      if (data.remainingPlayerId === this.currentPlayerId) {
        this.$config.logger.debug('🎯 当前用户被检测为单人房间，准备自动返回公聊并显示提示');
        
        // 获取房间邀请信息
        const inviteInfo = this.roomInviteInfo.get(data.roomId);
        this.$config.logger.debug('🔍 房间邀请信息:', inviteInfo);
        
        // 延迟一点显示，确保状态已更新
        setTimeout(() => {
          if (!this.showAloneRoomDialog) {
            this.aloneRoomId = data.roomId;
            this.aloneRoomInviteInfo = inviteInfo;
            this.showAloneRoomDialog = true;
            
            // 自动执行返回公聊的逻辑，但先显示提示
            this.$config.logger.debug('🔄 自动返回公聊房间');
            this.autoReturnToPublicChat(data.roomId);
            
            this.$config.logger.debug('💬 显示单人房间提示对话框');
          }
        }, 500);
      }
    },

    // 自动返回公聊房间
    autoReturnToPublicChat(roomId) {
      this.$config.logger.debug('🔄 自动从私聊回到公聊:', roomId);
      
      // 离开私聊房间
      this.leavePrivateVoiceRoom(roomId);
      
      // 更新本地状态 - 关闭房间
      this.$store.commit('voiceChat/updateChatRoom', {
        type: 'private',
        roomId,
        data: { 
          active: false,
          participants: []
        }
      });
      
      // 清除房间邀请信息
      this.roomInviteInfo.delete(roomId);
      
      // 通过WebSocket同步房间关闭
      this.sendVoiceChatMessage('voice_private_room_update', {
        action: 'closed',
        roomId,
        reason: 'alone_auto_return'
      });
      
      // 清除缓存，确保UI立即更新
      this.clearPrivateChatPlayersCache();
      this.expandedRoomsCount++;
      this.$forceUpdate();
    },

    // 切换页签
    switchTab(tab) {
      this.activeTab = tab;
    },
    
    // 切换主页签
    switchMainTab(tab) {
      this.activeMainTab = tab;
      
      // 如果切换到私人聊天页签，立即刷新状态避免显示延迟
      if (tab === 'private') {
        this.$config.logger.debug('🔄 切换到私人聊天页签，立即刷新状态');
        this.clearPrivateChatPlayersCache();
        
        // 强制两个方法同步重新计算
        this.getPlayersInPrivateChats();
        this.expandedRoomsCount++;
        this.$forceUpdate();
        
        // 使用nextTick确保DOM更新完成后再检查状态
        this.$nextTick(() => {
        // 检查状态一致性
        this.checkInconsistencyState();
          
          // 如果仍有房间信息未显示，再次强制更新
          const hasPrivateChats = this.allActivePrivateChats.length > 0;
          const hasPrivatePlayers = this.getPlayersInPrivateChats().length > 0;
          
          if (hasPrivatePlayers && !hasPrivateChats) {
            this.$config.logger.debug('🔄 检测到房间信息未完全显示，再次强制更新');
            this.expandedRoomsCount++;
            this.$forceUpdate();
          }
        });
      }
    },
    
    // 全员静音
    async muteAllPlayers() {
      this.players.forEach(player => {
        if (player.id !== this.currentPlayerId) {
          this.setPlayerMute(player.id, true);
        }
      });
      // 广播给所有客户端
      this.sendVoiceChatMessage('voice_storyteller_control', {
        action: 'global_mute',
        muted: true
      });
      // 同步全局静音标志
      try {
        await this.setGlobalMute({ muted: true });
      } catch (e) {
        // 兼容旧版本：如果动作不可用，直接提交全局权限到store
        if (this.$store && this.$store.commit) {
          this.$store.commit('voiceChat/setGlobalPermissions', { globalMute: true });
        }
      }
      this._ensureControlTabVisible();
      this.showToast('info', '已静音所有玩家');
    },
    
    // 取消全员静音
    async unmuteAllPlayers() {
      this.players.forEach(player => {
        this.setPlayerMute(player.id, false);
      });
      this.sendVoiceChatMessage('voice_storyteller_control', {
        action: 'global_mute',
        muted: false
      });
      try { 
        await this.setGlobalMute({ muted: false }); 
      } catch (e) { 
        if (this.$store && this.$store.commit) {
          this.$store.commit('voiceChat/setGlobalPermissions', { globalMute: false });
        }
      }
      this._ensureControlTabVisible();
      this.showToast('info', '已取消所有玩家静音');
    },
    
    // 在状态条高度变化后，确保控制列表仍在可视区域
    _ensureControlTabVisible() {
      this.$nextTick(() => {
        const container = this.$el && this.$el.querySelector('.voice-content');
        if (container) {
          // 直接滚动到底部，确保玩家控制列表始终可见
          container.scrollTop = container.scrollHeight;
        }
      });
    },
    
    // 切换玩家静音状态（同步广播）
    togglePlayerMute(playerId) {
      // 全员禁言时不允许单独解禁：改为浮层提示（2秒自动消失）
      if (this.globalMute) {
        this._showCenterBanner('全员禁言中，无法对单个玩家解禁');
        return;
      }
      const currentMuted = !!(this.playerPermissions[playerId]?.isMuted);
      const nextMuted = !currentMuted;
      this.setPlayerMute(playerId, nextMuted);
      // 广播到所有客户端
      this.sendVoiceChatMessage('voice_storyteller_control', {
        action: nextMuted ? 'mute_player' : 'unmute_player',
        playerId,
        playerName: this.getPlayerName(playerId),
        muted: nextMuted
      });
      const playerName = this.getPlayerName(playerId);
      this._showCenterBanner(`${playerName} ${nextMuted ? '已静音' : '已取消禁言'}`);
      // 对应玩家处也弹出提示
      this.$emit('voice-toast', { playerId, text: nextMuted ? '被禁言' : '取消禁言' });
    },

    // 居中提示条（2秒自动消失）
    _showCenterBanner(text) {
      this.centerBanner.text = text;
      this.centerBanner.show = false; // 先置为false以重置transition
      this.$nextTick(() => {
        this.centerBanner.show = true;
        clearTimeout(this._centerBannerTimer);
        this._centerBannerTimer = setTimeout(() => {
          this.centerBanner.show = false;
        }, 2000);
      });
    },
    
    // 设置玩家静音状态
    setPlayerMute(playerId, muted) {
      this.$store.commit('voiceChat/setPlayerMute', { playerId, muted });
    },
  },
  
  mounted() {
    // 初始化中心模态控制状态
    this.inviteModalState = { visible: false };
    this.joinApprovalState = { visible: false };

    // 监听store变化以触发本地模态
    this.$watch(
      () => this.$store.state.voiceChat.incomingInvite,
      (val) => {
        if (val) {
          this.inviteModalState = { visible: true };
        }
      },
      { deep: false }
    );
    this.$watch(
      () => this.$store.state.voiceChat.joinApproval,
      (val) => {
        if (val) {
          this.joinApprovalState = { visible: true };
        }
      },
      { deep: false }
    );
    // 组件挂载初始化
    
    // 乐观设定自身角色，并监听会话变更
    try {
      this._applyOptimisticRole();
      this.$watch(() => this.$store.state.session.playerId, () => this._applyOptimisticRole());
      this.$watch(() => this.$store.state.session.isSpectator, () => this._applyOptimisticRole());
    } catch (e) { /* 忽略 */ }

    // 初始化语音聊天
    this.initializeVoiceChat();
    
    // 检查移动端
    this.checkMobile();
    window.addEventListener('resize', this.checkMobile);
    
    // 创建事件总线用于跨组件通信
    if (!window.voiceChatEventBus) {
      window.voiceChatEventBus = new this.$root.constructor();
    }
    
    // 启动定期UI刷新定时器（每10秒检查一次状态一致性，减少频率）
    this.uiRefreshTimer = setInterval(() => {
      const playersInPrivate = this.getPlayersInPrivateChats();
      const activeRooms = this.allActivePrivateChats;
      
      // 如果检测到状态不一致，强制刷新
      if (playersInPrivate.length > 0 && activeRooms.length === 0) {
        this.$config.logger.debug('⏰ 定时检测到状态不一致，触发强制刷新');
        this.clearPrivateChatPlayersCache();
        this.expandedRoomsCount++;
        this.$forceUpdate();
      }
    }, 10000);
    
    // 监听来自其他客户端的说话状态变化
    window.voiceChatEventBus.$on('speaking-state-changed', this.handleRemoteSpeakingState);
    
    // 监听WebRTC信令消息
    window.voiceChatEventBus.$on('webrtc-signaling', this.handleWebRTCSignaling);
    
    // 监听其他玩家加入公共聊天（需要建立连接）
    window.voiceChatEventBus.$on('player-joined-public-chat', this.handlePlayerJoinedPublicChat);
    // 监听自身被广播加入公共聊天（例如后端在房主退出私聊后广播）
    window.voiceChatEventBus.$on('self-joined-public-chat', async () => {
      try {
        // 确保语音引擎可用
        if (!this.voiceEngine) {
          const ok = await this.initializeRealVoiceEngine();
          if (!ok) return;
        }
        // 如果尚未在公聊语音房间中，则加入
        if (!this.voiceEngine.getCurrentRooms().includes('public')) {
          await this.joinPublicVoiceRoom();
        }
      } catch (e) { /* 忽略 */ }
    });
    
    // 监听语音聊天状态同步（玩家连接时获取现有参与者）
    window.voiceChatEventBus.$on('voice-chat-state-synced', this.handleVoiceChatStateSynced);
    
    // 监听私聊房间创建事件
    window.voiceChatEventBus.$on('private-room-created', this.handlePrivateRoomCreated);
    // 监听私聊房间同步事件（新增参与者等）
    window.voiceChatEventBus.$on('private-room-sync', this.handlePrivateRoomSync);
    
    // 监听单人房间检查事件
    window.voiceChatEventBus.$on('check-alone-in-room', this.handleCheckAloneInRoom);
    
    // 监听私聊更新事件（玩家离开等）
    window.voiceChatEventBus.$on('private-chat-updated', this.handlePrivateChatUpdated);
    
    // 开发模式下启动模拟
    if (this.testMode) {
      this.simulateVoiceActivity();
    }
  },
  
  beforeDestroy() {
    // 清理定时器
    if (this.uiRefreshTimer) {
      clearInterval(this.uiRefreshTimer);
    }
    if (this.inconsistencyCheckTimer) {
      clearTimeout(this.inconsistencyCheckTimer);
    }
    
    // 清理事件监听
    if (window.voiceChatEventBus) {
      window.voiceChatEventBus.$off('speaking-state-changed', this.handleRemoteSpeakingState);
      window.voiceChatEventBus.$off('webrtc-signaling', this.handleWebRTCSignaling);
      window.voiceChatEventBus.$off('player-joined-public-chat', this.handlePlayerJoinedPublicChat);
      window.voiceChatEventBus.$off('self-joined-public-chat');
      window.voiceChatEventBus.$off('voice-chat-state-synced', this.handleVoiceChatStateSynced);
      window.voiceChatEventBus.$off('private-room-created', this.handlePrivateRoomCreated);
      window.voiceChatEventBus.$off('private-room-sync', this.handlePrivateRoomSync);
      window.voiceChatEventBus.$off('check-alone-in-room', this.handleCheckAloneInRoom);
      window.voiceChatEventBus.$off('private-chat-updated', this.handlePrivateChatUpdated);
    }
    window.removeEventListener('resize', this.checkMobile);
    
    // 销毁语音引擎
    if (this.voiceEngine) {
      this.voiceEngine.destroy();
      this.voiceEngine = null;
    }
    
    // 清理定时器
    if (this.connectionMonitorTimer) {
      clearInterval(this.connectionMonitorTimer);
    }
    if (this.voiceActivityTimer) {
      clearInterval(this.voiceActivityTimer);
    }
  }
};
</script>

<style lang="scss" scoped>
// 语音聊天容器样式
.voice-chat-container {
  position: relative;
}

/* 优化提示文案样式（更小、更精致） */
.room-controls {
  display: flex;
  justify-content: center;
  align-items: center;
}

.locked-tip {
  display: block;
  font-size: 12px;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.75);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  padding: 6px 10px;
  border-radius: 6px;
  text-align: center;
  margin: 0 auto;
}

@media (max-width: 768px) {
  .locked-tip {
    font-size: 11px;
    padding: 5px 8px;
    border-radius: 5px;
  }
}

// 主页签导航样式
.tabs-navigation {
  background: rgba(0, 0, 0, 0.3);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  padding: 8px 12px;
  
  .tabs-nav {
    display: flex;
    gap: 4px;
    
    .main-tab-btn {
      background: rgba(255, 255, 255, 0.1);
      border: 1px solid rgba(255, 255, 255, 0.2);
      color: rgba(255, 255, 255, 0.7);
      border-radius: 6px;
      padding: 8px 12px;
      cursor: pointer;
      font-size: 12px;
      transition: all 0.2s ease;
      display: flex;
      align-items: center;
      gap: 4px;
      flex: 1;
      justify-content: center;
      
      &:hover {
        background: rgba(255, 255, 255, 0.15);
        color: rgba(255, 255, 255, 0.9);
        transform: translateY(-1px);
      }
      
      &.active {
        background: rgba(255, 215, 0, 0.2);
        border-color: rgba(255, 215, 0, 0.4);
        color: #FFD700;
        font-weight: 600;
        box-shadow: 0 2px 8px rgba(255, 215, 0, 0.2);
      }
      
      .tab-badge {
        background: #ff4444;
        color: white;
        border-radius: 10px;
        padding: 1px 6px;
        font-size: 10px;
        font-weight: 600;
        margin-left: 4px;
        min-width: 16px;
        text-align: center;
      }
    }
  }
  
  // 移动端适配
  @media (max-width: 768px) {
    padding: 6px 8px;
    
    .tabs-nav {
      gap: 2px;
      
      .main-tab-btn {
        padding: 6px 8px;
        font-size: 11px;
        
        .tab-badge {
          font-size: 9px;
          padding: 1px 4px;
        }
      }
    }
  }
}

/* 优化 申请加入 按钮样式 */
.apply-join-btn {
  padding: 6px 10px;
  border-radius: 6px;
  background: rgba(76, 175, 80, 0.18);
  border: 1px solid rgba(76, 175, 80, 0.5);
  color: #c8f7c5;
  font-size: 12px;
  cursor: pointer;
  transition: all .2s ease;
  backdrop-filter: blur(2px);

  &:hover {
    background: rgba(76, 175, 80, 0.28);
    border-color: rgba(76, 175, 80, 0.8);
  }

  &:active {
    transform: translateY(1px);
  }
}

// 主页签内容区域样式
.tab-content-area {
  .main-tab-content {
    padding: 8px 0;
    
    h4 {
      color: #FFD700;
      margin: 0 0 8px 0;
      font-size: 13px;
      text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
      padding: 0 16px;
    }
  }
}

// 语音聊天面板样式
.voice-chat-panel {
  position: fixed;
  bottom: 20px;
  left: 20px;
  width: 320px;
  max-height: 400px;
  background: rgba(0, 0, 0, 0.85);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: 2px 2px 20px 1px #000;
  z-index: 998; // 稍低于ChatPanel，避免重叠时的层级问题
  font-family: inherit;
  overflow: hidden;
  transition: width 0.3s ease;
  
  // 统一风格：移除说书人橘色主题
  
  // 桌面端收起状态
  &.minimized:not(.mobile) {
    width: 140px; // 收起时大幅缩小宽度
    max-height: 60px;
    overflow: hidden;
    transition: width 0.3s ease, max-height 0.3s ease;
    
    .voice-content {
      display: none;
    }
    
    .voice-header {
      padding: 12px 12px; // 减少左右padding
      
      h3 {
        font-size: 14px; // 缩小标题字体
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      
      .voice-controls {
        gap: 4px; // 减少按钮间距
        
        .control-btn {
          padding: 4px 6px; // 缩小按钮padding
          font-size: 10px; // 缩小按钮字体
          min-width: auto;
          
          &.mic-btn {
            min-width: 32px; // 确保MIC按钮有足够空间
          }
          
          // 收起状态时隐藏说书人控制面板按钮，节省空间
          &.storyteller-panel-btn {
            display: none;
          }
        }
      }
    }
  }
  
  // 移动端适配
  @media (max-width: 768px) {
    position: fixed;
    bottom: 80px; // 避免与底部手势冲突
    left: 10px;
    right: auto;
    width: calc(100vw - 20px);
    max-width: 320px;
    max-height: 50vh;
    z-index: 1001;
    transition: max-height 0.3s ease;
    
    // 移动端参与者音量控制优化
    .participant-controls {
      max-width: 100px; // 移动端进一步限制宽度
      gap: 2px; // 更小的间距
    }
    
    .volume-control {
      max-width: 50px; // 移动端更小的音量控制
      
      .volume-slider {
        width: 30px; // 移动端更小的滑块
      }
      
      .volume-icon {
        font-size: 8px; // 移动端更小的图标
      }
    }
    
    &.minimized {
      max-height: 50px;
      bottom: 80px;
      
      .voice-content {
        opacity: 0;
        transform: translateY(-10px);
      }
    }
    
    &.expanded {
      max-height: 70vh;
      bottom: 20px;
      
      .voice-content {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    // 统一风格：移除说书人模式定位差异
    

  }
  
  // 平板适配
  @media (min-width: 769px) and (max-width: 1024px) {
    width: 280px;
    max-height: 350px;
    left: 15px;
    bottom: 15px;
  }
  
  // 大屏幕适配
  @media (min-width: 1025px) {
    left: 20px;
    bottom: 20px;
    
    // 当屏幕高度较小时，调整最大高度
    @media (max-height: 800px) {
      max-height: 350px;
    }
    
    // 当屏幕高度很小时，进一步调整
    @media (max-height: 600px) {
      max-height: 280px;
    }
  }
}

.voice-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 16px;
  background: rgba(0, 0, 0, 0.4);
  border-radius: 10px 10px 0 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  user-select: none;
  overflow: hidden; // 防止头部内容溢出
  
  h3 {
    color: #FFD700;
    margin: 0;
    font-size: 16px;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
    font-weight: 600;
    cursor: pointer;
    flex: 1;
    min-width: 0; // 允许标题被压缩
    white-space: nowrap; // 防止标题换行
    overflow: hidden; // 隐藏溢出的标题
    text-overflow: ellipsis; // 显示省略号
    
    &:hover {
      color: #FFED4E;
    }
  }
  
  .voice-controls {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-shrink: 0;
    margin-left: 12px;
  }
  
  .mic-btn {
    &.active {
      background: rgba(76, 175, 80, 0.3);
      border: 1px solid rgba(76, 175, 80, 0.5);
    }
    
    &.muted {
      background: rgba(244, 67, 54, 0.3);
      border: 1px solid rgba(244, 67, 54, 0.5);
      cursor: not-allowed;
      
      &:hover {
        background: rgba(244, 67, 54, 0.3);
      }
    }
    
    &:not(.active):not(.muted) {
      background: rgba(255, 255, 255, 0.2);
      border: 1px solid rgba(255, 255, 255, 0.3);
    }
  }
  
  .control-btn {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: rgba(255, 255, 255, 0.9);
    border-radius: 6px;
    padding: 6px 12px;
    cursor: pointer;
    font-size: 12px;
    line-height: 1;
    transition: all 0.15s ease;
    white-space: nowrap;
    position: relative;
    z-index: 10;
    min-width: 44px;
    min-height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    
    &:hover {
      background: rgba(255, 255, 255, 0.12);
      border-color: rgba(255, 255, 255, 0.25);
      transform: none;
    }
    
    &:active {
      transform: scale(0.98);
    }
    
    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
    
    // 移动端适配
    @media (max-width: 768px) {
      padding: 4px 8px;
      font-size: 11px;
      min-width: 36px;
      min-height: 28px;
    }
  }
  
  // 移动端头部优化
  @media (max-width: 768px) {
    padding: 10px 12px;
    cursor: pointer; // 整个头部可点击
    
    h3 {
      font-size: 14px;
    }
  }
}

.voice-content {
  transition: opacity 0.3s ease, transform 0.3s ease;
  overflow-y: auto;
  max-height: calc(100% - 50px);
  
  // 移动端内容区域
  @media (max-width: 768px) {
    &::-webkit-scrollbar {
      width: 3px;
    }
    
    &::-webkit-scrollbar-track {
      background: rgba(255, 255, 255, 0.05);
    }
    
    &::-webkit-scrollbar-thumb {
      background: rgba(255, 255, 255, 0.2);
      border-radius: 2px;
    }
  }
}



.global-status {
  margin: 0 16px 12px;
  
  .status-warning {
    background: radial-gradient(at 0 -15%, rgba(255, 255, 255, 0.07) 70%, rgba(255, 255, 255, 0) 71%) 0 0/80% 90% no-repeat content-box,
               linear-gradient(#f44336, rgba(244, 67, 54, 0.3)) content-box,
               linear-gradient(#292929, #5d1a1a) border-box;
    border: 1px solid #f44336;
    border-radius: 6px;
    padding: 8px 12px;
    color: white;
    font-size: 13px;
    text-align: center;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
    box-shadow: inset 0 1px 1px rgba(244, 67, 54, 0.6), 0 2px 8px rgba(0, 0, 0, 0.3);
    
    // 移动端适配
    @media (max-width: 768px) {
      font-size: 12px;
      padding: 6px 10px;
      margin: 0 12px 10px;
    }
  }
}

.chat-room {
  margin: 0 16px 12px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 8px;
  padding: 12px;
  background: rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(2px);
  
  &.public-chat {
    background: linear-gradient(135deg, rgba(33, 150, 243, 0.15), rgba(33, 150, 243, 0.05));
    border-color: rgba(33, 150, 243, 0.4);
    box-shadow: 0 2px 8px rgba(33, 150, 243, 0.1);
  }
  
  // 移动端适配
  @media (max-width: 768px) {
    margin: 0 12px 10px;
    padding: 10px;
  }
}

.room-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  
  .room-title {
    color: white;
    font-weight: 500;
    font-size: 14px;
  }
  
  .room-status {
    display: flex;
    align-items: center;
    gap: 8px;
    
    .participant-count {
      color: #999;
      font-size: 12px;
    }
    
    .private-chat-indicator {
      background: rgba(156, 39, 176, 0.8);
      color: white;
      padding: 2px 6px;
      border-radius: 10px;
      font-size: 10px;
      font-weight: 600;
      display: inline-flex;
      align-items: center;
      gap: 2px;
      animation: pulse 2s ease-in-out infinite;
      cursor: help;
      
      &:hover {
        background: rgba(156, 39, 176, 1);
        transform: scale(1.05);
        transition: all 0.2s ease;
      }
    }
    
    .connection-indicator {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      
      &.excellent {
        background: #4CAF50;
        box-shadow: 0 0 6px rgba(76, 175, 80, 0.6);
      }
      
      &.good {
        background: #FFC107;
        box-shadow: 0 0 6px rgba(255, 193, 7, 0.6);
      }
      
      &.poor {
        background: #f44336;
        box-shadow: 0 0 6px rgba(244, 67, 54, 0.6);
      }
    }
  }
}

.participants-list {
  margin-bottom: 12px;
}

.participant {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 8px;
  margin-bottom: 4px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.05);
  overflow: hidden; // 防止内容超出参与者行
  
  &.speaking {
    background: rgba(76, 175, 80, 0.2);
    border-left: 3px solid #4CAF50;
  }
  
  &.muted {
    opacity: 0.6;
    background: rgba(244, 67, 54, 0.1);
  }
  
  &.self {
    border: 1px solid rgba(255, 193, 7, 0.3);
  }
  
  .participant-name {
    color: white;
    font-size: 13px;
    flex: 1; // 让名字占用剩余空间
    min-width: 0; // 允许名字被压缩
    white-space: nowrap; // 防止名字换行
    overflow: hidden; // 隐藏超长名字
    text-overflow: ellipsis; // 显示省略号
    margin-right: 8px; // 与控件保持间距
  }
  
  .participant-controls {
    display: flex;
    align-items: center;
    gap: 3px; // 减少间距
    flex-shrink: 0; // 防止控件被压缩
    max-width: 120px; // 限制控件区域最大宽度
    overflow: hidden; // 防止控件溢出
    
    .speaking-indicator {
      font-size: 12px;
      animation: pulse 1s infinite;
      flex-shrink: 0; // 防止指示器被压缩
    }
    
    .mute-indicator {
      font-size: 12px;
      color: #f44336;
      flex-shrink: 0; // 防止指示器被压缩
    }
    
    .signal-strength {
      font-size: 10px;
      flex-shrink: 0; // 防止信号强度被压缩
      
      &.excellent { color: #4CAF50; }
      &.good { color: #FFC107; }
      &.poor { color: #f44336; }
    }
  }
}

.room-controls {
  display: flex;
  gap: 8px;
  margin-top: 8px;
  
  .join-btn, .leave-btn {
    flex: 1;
    padding: 8px 12px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 12px;
    font-weight: 500;
    transition: all 0.15s ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
  }
  
  .join-btn {
    background: rgba(76, 175, 80, 0.12);
    border: 1px solid rgba(76, 175, 80, 0.35);
    color: #a5d6a7;
    
    &:hover:not(:disabled) {
      background: rgba(76, 175, 80, 0.18);
      border-color: rgba(76, 175, 80, 0.45);
      transform: none;
    }
    
    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
      transform: none;
    }
  }
  
  .leave-btn {
    background: rgba(244, 67, 54, 0.12);
    border: 1px solid rgba(244, 67, 54, 0.35);
    color: #ff8a80;
    
    &:hover {
      background: rgba(244, 67, 54, 0.18);
      border-color: rgba(244, 67, 54, 0.45);
      transform: none;
    }
  }
  
  // 移动端适配
  @media (max-width: 768px) {
    gap: 6px;
    
    .join-btn, .leave-btn {
      padding: 6px 10px;
      font-size: 11px;
    }
  }
}

.private-chats {
  .section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
    
    > span {
      color: white;
      font-weight: 500;
      font-size: 14px;
    }
    
    .header-status {
      display: flex;
      align-items: center;
      gap: 8px;
      
      .moved-players-info {
        background: rgba(76, 175, 80, 0.2);
        border: 1px solid rgba(76, 175, 80, 0.4);
        color: #4CAF50;
        padding: 2px 6px;
        border-radius: 8px;
        font-size: 10px;
        font-weight: 600;
        display: inline-flex;
        align-items: center;
        gap: 2px;
        cursor: help;
        animation: slideInRight 0.5s ease;
        
        &:hover {
          background: rgba(76, 175, 80, 0.3);
          transform: scale(1.05);
          transition: all 0.2s ease;
        }
      }
    }
    
    .add-private-btn {
      background: rgba(255, 255, 255, 0.2);
      border: 1px solid rgba(255, 255, 255, 0.3);
      border-radius: 3px;
      width: 24px;
      height: 24px;
      cursor: pointer;
      color: white;
      font-size: 12px;
      transition: background-color 0.3s;
      display: flex;
      align-items: center;
      justify-content: center;
      
      &:hover {
        background: rgba(255, 255, 255, 0.3);
      }
    }
  }
}

// 私人聊天容器
.private-chats-container {
  max-height: 300px;
  overflow-y: auto;
  
  // 自定义滚动条
  &::-webkit-scrollbar {
    width: 4px;
  }
  
  &::-webkit-scrollbar-track {
    background: rgba(255, 255, 255, 0.1);
    border-radius: 2px;
  }
  
  &::-webkit-scrollbar-thumb {
    background: rgba(156, 39, 176, 0.5);
    border-radius: 2px;
    
    &:hover {
      background: rgba(156, 39, 176, 0.7);
    }
  }
}

.private-chat-room {
  background: rgba(156, 39, 176, 0.1);
  border: 1px solid rgba(156, 39, 176, 0.3);
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 12px;
  transition: all 0.3s ease;
  
  &.expanded {
    background: rgba(156, 39, 176, 0.15);
    border-color: rgba(156, 39, 176, 0.5);
    box-shadow: 0 4px 12px rgba(156, 39, 176, 0.2);
  }
  
  .private-room-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
    
    .room-id-info {
      display: flex;
      align-items: center;
      gap: 8px;
      cursor: pointer;
      padding: 6px 8px;
      border-radius: 6px;
      transition: all 0.2s ease;
      flex: 1;
      
      &:hover {
        background: rgba(255, 255, 255, 0.1);
      }
      
      .room-name {
        color: white;
        font-size: 13px;
        font-weight: 500;
        line-height: 1.3;
        flex: 1;
        margin-right: 8px;
        word-break: break-word;
      }
      
      .expand-icon {
        color: #FFD700;
        font-size: 10px;
        margin-left: auto;
        transition: transform 0.2s ease;
      }
    }
    
    .private-controls {
      display: flex;
      gap: 4px;
      
      .return-public-btn,
    .leave-private-btn {
        background: rgba(255, 255, 255, 0.1);
        border: 1px solid rgba(255, 255, 255, 0.2);
        border-radius: 4px;
        padding: 4px 8px;
      cursor: pointer;
      font-size: 12px;
        transition: all 0.2s ease;
      
      &:hover {
          background: rgba(255, 255, 255, 0.2);
          transform: translateY(-1px);
        }
      }
      
      .return-public-btn {
        color: #4CAF50;
        
        &:hover {
          border-color: #4CAF50;
          color: #4CAF50;
        }
      }
      
      .leave-private-btn {
      color: #f44336;
        
        &:hover {
          border-color: #f44336;
          color: #f44336;
        }
      }
    }
  }
  
  // 参与者容器
  .participants-container {
    margin-top: 8px;
    background: rgba(0, 0, 0, 0.2);
    border-radius: 6px;
    border: 1px solid rgba(156, 39, 176, 0.3);
    overflow: hidden;
    
    .participants-scroll-area {
      max-height: 200px;
      overflow-y: auto;
      
      // 自定义滚动条
      &::-webkit-scrollbar {
        width: 3px;
      }
      
      &::-webkit-scrollbar-track {
        background: rgba(255, 255, 255, 0.05);
      }
      
      &::-webkit-scrollbar-thumb {
        background: rgba(156, 39, 176, 0.6);
        border-radius: 2px;
        
        &:hover {
          background: rgba(156, 39, 176, 0.8);
        }
      }
      
      .participants-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
        padding: 8px 12px;
        background: rgba(156, 39, 176, 0.1);
        border-bottom: 1px solid rgba(156, 39, 176, 0.2);
        
        .header-title {
          color: #E1BEE7;
          font-size: 11px;
          font-weight: 600;
        }
        
        .connection-status {
          font-size: 10px;
          color: rgba(255, 255, 255, 0.7);
        }
      }
      
      .participants-list {
        padding: 4px;
        
        .participant-item {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding: 6px 8px;
          margin: 2px 0;
          border-radius: 4px;
          transition: all 0.2s ease;
    
    &.speaking {
      background: rgba(76, 175, 80, 0.2);
            border: 1px solid rgba(76, 175, 80, 0.4);
            animation: pulse 1.5s ease-in-out infinite;
    }
    
    &.self {
            background: rgba(255, 215, 0, 0.1);
            border: 1px solid rgba(255, 215, 0, 0.3);
          }
          
          &:hover {
            background: rgba(255, 255, 255, 0.05);
          }
          
          .participant-info {
            display: flex;
            align-items: center;
            gap: 6px;
            flex: 1;
    
    .participant-name {
      color: white;
      font-size: 12px;
              font-weight: 500;
    }
    
            .participant-badges {
      display: flex;
      gap: 4px;
      
              .self-badge {
                background: #FFD700;
                color: #000;
                padding: 1px 4px;
                border-radius: 3px;
                font-size: 9px;
                font-weight: 600;
              }
              
              .speaking-badge {
        font-size: 10px;
                animation: bounce 1s ease-in-out infinite;
              }
            }
      }
          
          .participant-status {
            display: flex;
            align-items: center;
            gap: 6px;
      
      .signal-strength {
              font-size: 10px;
              opacity: 0.7;
        
        &.excellent { color: #4CAF50; }
              &.good { color: #8BC34A; }
              &.fair { color: #FFC107; }
              &.poor { color: #FF9800; }
              &.bad { color: #f44336; }
            }
            
            .volume-indicator {
              width: 20px;
              height: 3px;
              background: rgba(255, 255, 255, 0.1);
              border-radius: 2px;
              overflow: hidden;
              
              .volume-bar {
                height: 100%;
                background: linear-gradient(90deg, #4CAF50, #8BC34A, #FFC107);
                border-radius: 2px;
                transition: width 0.1s ease;
              }
            }
          }
        }
      }
      
      .chat-stats {
        padding: 8px 12px;
        background: rgba(0, 0, 0, 0.1);
        border-top: 1px solid rgba(156, 39, 176, 0.2);
        
        .stat-item {
          display: flex;
          justify-content: space-between;
          margin: 2px 0;
          font-size: 10px;
          
          .stat-label {
            color: rgba(255, 255, 255, 0.6);
          }
          
          .stat-value {
            color: white;
            font-weight: 500;
            
            &.connected { color: #4CAF50; }
            &.connecting { color: #FFC107; }
            &.disconnected { color: #f44336; }
          }
        }
      }
    }
  }
}

// 动画效果
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

@keyframes bounce {
  0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
  40% { transform: translateY(-2px); }
  60% { transform: translateY(-1px); }
}

@keyframes slideInRight {
  from {
    opacity: 0;
    transform: translateX(20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}



.chat-request {
  background: rgba(255, 193, 7, 0.1);
  border: 1px solid rgba(255, 193, 7, 0.3);
  border-radius: 6px;
  padding: 8px;
  margin-bottom: 8px;
  
  .request-content {
    display: flex;
    justify-content: space-between;
    align-items: center;
    
    span {
      color: white;
      font-size: 13px;
    }
    
    .request-actions {
      display: flex;
      gap: 4px;
      
      .approve-btn, .reject-btn {
        background: none;
        border: none;
        cursor: pointer;
        font-size: 14px;
        
        &:hover {
          opacity: 0.7;
        }
      }
    }
  }
}

.mic-volume-control {
  display: flex;
  align-items: center;
  margin-left: 8px;
  
  .mic-volume-slider {
    width: 60px;
    height: 4px;
    background: rgba(255, 255, 255, 0.2);
    border-radius: 2px;
    outline: none;
    cursor: pointer;
    
    &::-webkit-slider-thumb {
      appearance: none;
      width: 12px;
      height: 12px;
      border-radius: 50%;
      background: #007bff;
      cursor: pointer;
      border: 1px solid rgba(255, 255, 255, 0.3);
      
      &:hover {
        background: #0056b3;
        transform: scale(1.1);
      }
    }
    
    &::-moz-range-thumb {
      width: 12px;
      height: 12px;
      border-radius: 50%;
      background: #007bff;
      cursor: pointer;
      border: 1px solid rgba(255, 255, 255, 0.3);
      
      &:hover {
        background: #0056b3;
        transform: scale(1.1);
      }
    }
  }
}

.volume-control {
  display: flex;
  align-items: center;
  margin-left: 4px; // 减少左边距
  gap: 2px; // 减少间距
  max-width: 60px; // 限制最大宽度
  flex-shrink: 0; // 防止被压缩
  
  .volume-slider {
    width: 40px; // 减少滑块宽度
    height: 3px;
    background: rgba(255, 255, 255, 0.2);
    border-radius: 2px;
    outline: none;
    cursor: pointer;
    flex-shrink: 0; // 防止滑块被压缩
    
    &::-webkit-slider-thumb {
      appearance: none;
      width: 10px;
      height: 10px;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.8);
      cursor: pointer;
      
      &:hover {
        background: white;
        transform: scale(1.1);
      }
    }
    
    &::-moz-range-thumb {
      width: 10px;
      height: 10px;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.8);
      cursor: pointer;
      border: none;
      
      &:hover {
        background: white;
        transform: scale(1.1);
      }
    }
  }
  
  .volume-icon {
    font-size: 10px; // 减小图标大小
    opacity: 0.7;
    flex-shrink: 0; // 防止图标被压缩
  }
}

// .test-mode-indicator 已废弃

// 独立的右侧多功能面板样式
.multi-function-panel {
  position: fixed;
  bottom: 20px;
  left: 360px; // 位于主语音面板右侧
  width: 350px;
  max-height: 450px;
  background: rgba(0, 0, 0, 0.85);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  box-shadow: 2px 2px 20px 1px #000;
  z-index: 997; // 稍低于主语音面板
  font-family: inherit;
  overflow: hidden;
  transition: all 0.3s ease;
  
  // 统一风格：移除说书人橘色主题
  
  // 移动端适配
  @media (max-width: 768px) {
    position: fixed;
    bottom: 80px;
    left: 10px;
    right: 10px;
    left: auto;
    width: calc(100vw - 20px);
    max-width: 350px;
    max-height: 40vh;
    z-index: 1000;
    
    &.minimized {
      max-height: 50px;
      bottom: 140px; // 避免与主面板重叠
    }
  }
  
  // 平板适配
  @media (min-width: 769px) and (max-width: 1024px) {
    left: 310px;
    width: 320px;
    max-height: 400px;
  }
  
  // 大屏幕适配
  @media (min-width: 1200px) {
    left: 380px;
    width: 380px;
    max-height: 500px;
  }
}

// 多功能面板头部和页签样式
.panel-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  padding: 8px 12px;
  background: rgba(0, 0, 0, 0.4);
  border-radius: 10px 10px 0 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  user-select: none;
  
  .tabs-nav {
    display: flex;
    gap: 4px;
    flex: 1;
    
    .tab-btn {
      background: rgba(255, 255, 255, 0.1);
      border: 1px solid rgba(255, 255, 255, 0.2);
      color: rgba(255, 255, 255, 0.7);
      border-radius: 6px;
      padding: 6px 12px;
      cursor: pointer;
      font-size: 12px;
      transition: all 0.2s ease;
      display: flex;
      align-items: center;
      gap: 4px;
      
      &:hover {
        background: rgba(255, 255, 255, 0.15);
        color: rgba(255, 255, 255, 0.9);
      }
      
      &.active {
        background: rgba(255, 215, 0, 0.2);
        border-color: rgba(255, 215, 0, 0.4);
        color: #FFD700;
        font-weight: 600;
      }
      
      .badge {
        background: #ff4444;
      color: white;
        border-radius: 10px;
        padding: 1px 6px;
        font-size: 10px;
        font-weight: 600;
        margin-left: 4px;
      }
    }
  }
  
  .control-btn {
    background: none;
    border: 1px solid rgba(255, 255, 255, 0.2);
    color: #fff;
    border-radius: 6px;
    padding: 4px 8px;
    cursor: pointer;
    font-size: 12px;
    transition: all 0.2s ease;
    
    &:hover {
      background: rgba(255, 255, 255, 0.1);
    }
    
    &.expanded {
      background: rgba(255, 215, 0, 0.1);
      border-color: rgba(255, 215, 0, 0.3);
      color: #FFD700;
    }
  }
}

.monitor-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 16px;
  background: rgba(0, 0, 0, 0.4);
  border-radius: 10px 10px 0 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  user-select: none;
  
  h3 {
    color: #FFD700;
    margin: 0;
    font-size: 16px;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
    font-weight: 600;
    flex: 1;
  }
  
  .monitor-controls {
    display: flex;
    align-items: center;
    gap: 12px;
    
    .stats {
      .active-chats-count {
        color: #FFD700;
        font-size: 11px;
        font-weight: 600;
        background: rgba(255, 215, 0, 0.1);
        padding: 2px 6px;
        border-radius: 3px;
        border: 1px solid rgba(255, 215, 0, 0.3);
      }
    }
    
    .control-btn {
      background: none;
      border: 1px solid rgba(255, 255, 255, 0.2);
      color: #fff;
      border-radius: 6px;
      padding: 4px 8px;
      cursor: pointer;
      font-size: 12px;
      transition: all 0.2s ease;
      
      &:hover {
        background: rgba(255, 255, 255, 0.1);
      }
      
      &.expanded {
        background: rgba(255, 215, 0, 0.1);
        border-color: rgba(255, 215, 0, 0.3);
        color: #FFD700;
      }
    }
  }
  
  // 移动端适配
  @media (max-width: 768px) {
    padding: 10px 12px;
    
    h3 {
      font-size: 14px;
    }
  }
}

// 面板内容样式
.panel-content {
  padding: 12px 16px;
  max-height: calc(100% - 60px);
  overflow-y: auto;
}
  
// 语音控制页签样式
.main-tab-content.control-tab {
      .personal-control-section,
      .voice-control-section,
      .player-control-section {
        margin-bottom: 16px;
        
        h4 {
          color: #FFD700;
          margin: 0 0 8px 0;
          font-size: 13px;
          text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
        }
      }
      
      .global-controls {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 8px;
        margin: 8px 0 12px;
        
        .control-action-btn {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          gap: 6px;
          background: rgba(255, 255, 255, 0.06);
          border: 1px solid rgba(255, 255, 255, 0.18);
          border-radius: 6px;
          padding: 6px 10px;
          cursor: pointer;
          font-size: 12px;
          line-height: 1;
          color: rgba(255, 255, 255, 0.9);
          transition: all 0.15s ease;
          position: relative;
          overflow: hidden;
          
          &:hover {
            background: rgba(255, 255, 255, 0.12);
            transform: none;
          }
          
          &.mute-all-btn:hover {
            background: rgba(244, 67, 54, 0.12);
            border-color: rgba(244, 67, 54, 0.35);
            color: #ff8a80;
          }
          
          &.unmute-all-btn:hover {
            background: rgba(76, 175, 80, 0.12);
            border-color: rgba(76, 175, 80, 0.35);
            color: #a5d6a7;
          }

          &.active {
            box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.04);
          }

          &:disabled {
            opacity: 0.55;
            cursor: not-allowed;
            filter: grayscale(10%);
          }
        }
      }

      // .global-state-indicator 已移除，顶部已有全局提示
      
      .personal-controls {
        .mic-status {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 8px;
          padding: 6px 8px;
          background: rgba(255, 255, 255, 0.05);
          border-radius: 4px;
          
          .status-label {
            color: rgba(255, 255, 255, 0.7);
            font-size: 12px;
          }
          
          .status-value {
            font-size: 12px;
            font-weight: 500;
            color: rgba(255, 255, 255, 0.6);
            
            &.active {
              color: #4CAF50;
            }
          }
        }
        
        .volume-control {
          display: flex;
          align-items: center;
          gap: 8px;
          padding: 6px 8px;
          background: rgba(255, 255, 255, 0.05);
          border-radius: 4px;
          
          .volume-label {
            color: rgba(255, 255, 255, 0.7);
            font-size: 12px;
            min-width: 40px;
          }
          
          .volume-slider {
            flex: 1;
            height: 4px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 2px;
            outline: none;
            
            &::-webkit-slider-thumb {
              appearance: none;
              width: 12px;
              height: 12px;
              background: #FFD700;
              border-radius: 50%;
              cursor: pointer;
            }
          }
          
          .volume-value {
            color: #FFD700;
            font-size: 11px;
            font-weight: 600;
            min-width: 35px;
            text-align: right;
          }
        }
      }
      
      .non-storyteller-info {
        padding: 12px;
        background: rgba(255, 255, 255, 0.02);
        border-radius: 6px;
        border: 1px dashed rgba(255, 255, 255, 0.1);
        
        .info-item {
          display: flex;
          align-items: center;
          gap: 8px;
          margin-bottom: 6px;
          
          &:last-child {
            margin-bottom: 0;
          }
          
          .info-icon {
            font-size: 14px;
            min-width: 20px;
          }
          
          .info-text {
            color: rgba(255, 255, 255, 0.7);
            font-size: 12px;
          }
        }
      }
      
      .players-control-list {
        max-height: 40vh; // 自适应视口高度，避免被遮挡
        overflow-y: auto;
        padding-right: 4px;

        &::-webkit-scrollbar { width: 6px; }
        &::-webkit-scrollbar-track { background: rgba(255,255,255,0.06); border-radius: 3px; }
        &::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.25); border-radius: 3px; }
        
        .player-control-item {
          display: grid;
          grid-template-columns: 1fr auto;
          align-items: center;
          gap: 8px;
          padding: 6px 10px;
          margin-bottom: 6px;
          background: rgba(255, 255, 255, 0.05);
          border-radius: 6px;
          border-left: 3px solid transparent;
          transition: background 0.15s ease, border-color 0.15s ease;
          
          &.muted {
            border-left-color: #f44336;
            background: rgba(244, 67, 54, 0.1);
          }
          
          &.speaking {
            border-left-color: #4CAF50;
            background: rgba(76, 175, 80, 0.1);
            
            .player-name {
              color: #4CAF50;
              text-shadow: 0 0 3px rgba(76, 175, 80, 0.5);
            }
          }
          
          .player-name {
            color: white;
            font-size: 12px;
            font-weight: 500;
            flex: 1;
          }
          
          .player-actions {
            display: inline-flex;
            align-items: center;
            gap: 6px;

            .action-btn {
              display: inline-flex;
              align-items: center;
              justify-content: center;
              background: rgba(255, 255, 255, 0.06);
              border: 1px solid rgba(255, 255, 255, 0.18);
              border-radius: 4px;
              padding: 4px 8px;
              cursor: pointer;
              font-size: 12px;
              line-height: 1;
              color: rgba(255, 255, 255, 0.9);
              transition: all 0.15s ease;
              min-width: 32px;
              min-height: 24px;
              
              &:hover {
                background: rgba(255, 255, 255, 0.12);
              }
              
              &.active {
                background: rgba(244, 67, 54, 0.12);
                border-color: rgba(244, 67, 54, 0.35);
                color: #ff8a80;
                
                &:hover {
                  background: rgba(244, 67, 54, 0.18);
                }
              }
            }
          }
        }
        .no-controllable {
          padding: 10px 8px;
          color: rgba(255,255,255,0.65);
          font-size: 12px;
          text-align: center;
          background: rgba(255,255,255,0.04);
          border: 1px dashed rgba(255,255,255,0.12);
          border-radius: 6px;
        }
      }
    }
    
// 监控页签样式  
.main-tab-content.monitor-tab {
      .my-chats-section,
      .all-chats-section {
        margin-bottom: 16px;
        
        h4 {
          color: #FFD700;
          margin: 0 0 8px 0;
          font-size: 13px;
          text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
        }
      }
      
      .no-my-chats {
    text-align: center;
        padding: 12px 8px;
    color: rgba(255, 255, 255, 0.6);
    font-style: italic;
    font-size: 12px;
        background: rgba(255, 255, 255, 0.02);
        border-radius: 6px;
        border: 1px dashed rgba(255, 255, 255, 0.1);
  }
}

.monitor-content {
  padding: 12px 16px;
  max-height: calc(100% - 60px);
  overflow-y: auto;
  
  .no-private-chats {
    text-align: center;
    padding: 20px 8px;
    color: rgba(255, 255, 255, 0.6);
    font-style: italic;
    font-size: 13px;
    background: rgba(255, 255, 255, 0.02);
    border-radius: 6px;
    border: 1px dashed rgba(255, 255, 255, 0.1);
  }
  
  .all-private-chats-list {
    max-height: 280px;
    overflow-y: auto;
    margin-bottom: 12px;
    
    &::-webkit-scrollbar {
      width: 6px;
    }
    
    &::-webkit-scrollbar-track {
      background: rgba(255, 255, 255, 0.1);
      border-radius: 3px;
    }
    
    &::-webkit-scrollbar-thumb {
      background: rgba(255, 255, 255, 0.3);
      border-radius: 3px;
      
      &:hover {
        background: rgba(255, 255, 255, 0.5);
      }
    }
  }
  
  .displayed-private-chat {
    background: rgba(255, 255, 255, 0.05);
    border-radius: 6px;
    padding: 10px 12px;
    margin-bottom: 8px;
    border-left: 3px solid transparent;
    transition: all 0.3s ease;
    position: relative;
    
    &.own-chat {
      border-left-color: #4CAF50;
      background: rgba(76, 175, 80, 0.1);
      box-shadow: 0 2px 8px rgba(76, 175, 80, 0.1);
    }
    
    &.others-chat {
      border-left-color: #2196F3;
      background: rgba(33, 150, 243, 0.1);
      box-shadow: 0 2px 8px rgba(33, 150, 243, 0.1);
    }
    
    &:hover {
      background: rgba(255, 255, 255, 0.1);
      transform: translateY(-1px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    }
    
    &:last-child {
      margin-bottom: 0;
    }
  }
  
  .chat-display-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 6px;
  }
  
  .chat-participants {
    flex: 1;
    
    .participant-display {
      color: white;
      font-size: 13px;
      font-weight: 500;
      
      &.speaking {
        color: #4CAF50;
        font-weight: 600;
        text-shadow: 0 0 3px rgba(76, 175, 80, 0.5);
        animation: pulse 1s infinite;
      }
      
      &.self {
        color: #FFD700;
        font-weight: 600;
      }
    }
  }
  
  .chat-status {
    display: flex;
    align-items: center;
    gap: 8px;
    
    .connection-indicator {
      width: 10px;
      height: 10px;
      border-radius: 50%;
      box-shadow: 0 0 4px currentColor;
      
      &.excellent { 
        background: #4CAF50;
        color: #4CAF50;
      }
      &.good { 
        background: #FFC107;
        color: #FFC107;
      }
      &.poor { 
        background: #f44336;
        color: #f44336;
      }
    }
    
    .duration {
      color: rgba(255, 255, 255, 0.7);
      font-size: 11px;
      font-family: monospace;
      min-width: 40px;
      text-align: right;
      background: rgba(0, 0, 0, 0.3);
      padding: 2px 6px;
      border-radius: 3px;
    }
  }
  
  .chat-activity {
    margin-top: 6px;
    
    .activity-indicator {
      color: #4CAF50;
      font-size: 11px;
      font-weight: 500;
      animation: pulse 1.5s infinite;
      background: rgba(76, 175, 80, 0.1);
      padding: 2px 6px;
      border-radius: 3px;
      border: 1px solid rgba(76, 175, 80, 0.3);
    }
  }
  
  .chat-actions {
    display: flex;
    gap: 6px;
    margin-top: 8px;
    justify-content: flex-end;
    
    .action-btn {
      background: rgba(255, 255, 255, 0.1);
      border: 1px solid rgba(255, 255, 255, 0.2);
      border-radius: 4px;
      padding: 4px 8px;
      cursor: pointer;
      font-size: 12px;
      transition: all 0.2s ease;
      
      &:hover {
        background: rgba(255, 255, 255, 0.2);
        transform: scale(1.05);
      }
      
      &.mute-btn:hover {
        background: rgba(244, 67, 54, 0.2);
        border-color: rgba(244, 67, 54, 0.4);
      }
      
      &.info-btn:hover {
        background: rgba(33, 150, 243, 0.2);
        border-color: rgba(33, 150, 243, 0.4);
      }
    }
  }
  
  .monitor-stats {
    margin-top: 12px;
    padding: 8px 12px;
    background: rgba(0, 0, 0, 0.3);
    border-radius: 6px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    
    .stat-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 4px;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      .stat-label {
        color: rgba(255, 255, 255, 0.7);
        font-size: 11px;
      }
      
      .stat-value {
        color: #FFD700;
        font-size: 11px;
        font-weight: 600;
        font-family: monospace;
      }
    }
  }
  
  // 移动端内容适配
  @media (max-width: 768px) {
    padding: 10px 12px;
    
    .all-private-chats-list {
      max-height: 200px;
    }
    
    .displayed-private-chat {
      padding: 8px 10px;
      margin-bottom: 6px;
    }
    
    .chat-participants .participant-display {
      font-size: 12px;
    }
  }
}

/* 居中浮层提示（全局） */
.center-voice-banner {
  position: fixed;
  left: 50%;
  top: 35%;
  transform: translateX(-50%) translateY(20px);
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  padding: 10px 16px;
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,0.2);
  font-size: 14px;
  z-index: 2000;
  opacity: 0;
  transition: opacity .25s ease, transform .25s ease;
  pointer-events: none;
}
.center-voice-banner.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.center-voice-banner.hide { opacity: 0; transform: translateX(-50%) translateY(-10px); }

// 没有参与者时的提示样式
.no-participants,
.no-private-chats {
  text-align: center;
  padding: 16px 12px;
  color: rgba(255, 255, 255, 0.6);
  background: rgba(255, 255, 255, 0.02);
  border-radius: 6px;
  border: 1px dashed rgba(255, 255, 255, 0.1);
  margin: 8px 16px;

  span {
    display: block;
    font-size: 14px;
    margin-bottom: 6px;
    color: rgba(255, 255, 255, 0.8);
  }

  p {
    margin: 0;
    font-size: 12px;
    font-style: italic;
    color: rgba(255, 255, 255, 0.5);
  }
}

// 私聊参与者列表折叠样式
.private-room-participants {
  margin-top: 8px;
  
  .participants-toggle {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 4px 8px;
    background: rgba(255, 255, 255, 0.05);
    border-radius: 3px;
    cursor: pointer;
    transition: background-color 0.3s;
    
    &:hover {
      background: rgba(255, 255, 255, 0.1);
    }
    
    .toggle-text {
      color: white;
      font-size: 11px;
      font-weight: 500;
    }
    
    .toggle-icon {
      color: rgba(255, 255, 255, 0.7);
      font-size: 10px;
      transition: transform 0.3s;
    }
  }
  
  .private-participants-list {
    margin-top: 4px;
    max-height: 120px;
    overflow-y: auto;
    
    &::-webkit-scrollbar {
      width: 3px;
    }
    
    &::-webkit-scrollbar-track {
      background: rgba(255, 255, 255, 0.1);
      border-radius: 2px;
    }
    
    &::-webkit-scrollbar-thumb {
      background: rgba(255, 255, 255, 0.3);
      border-radius: 2px;
    }
  }
}

// 回到公聊按钮样式
.return-public-btn {
  background: rgba(76, 175, 80, 0.2);
  border: 1px solid rgba(76, 175, 80, 0.4);
  border-radius: 3px;
  width: 20px;
  height: 20px;
  cursor: pointer;
  color: #4CAF50;
  font-size: 10px;
  transition: all 0.3s;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 4px;
  
  &:hover {
    background: rgba(76, 175, 80, 0.3);
    transform: scale(1.05);
  }
  
  &:active {
    transform: scale(0.95);
  }
}

// 动画
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

// 单人房间提示对话框样式
.alone-room-dialog {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10002; // 比其他对话框层级更高
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  
  .dialog-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(3px);
  }
  
  .dialog-content {
    position: relative;
    background: rgba(0, 0, 0, 0.9);
    border-radius: 12px;
    width: 90vw;
    max-width: 420px;
    border: 1px solid rgba(255, 215, 0, 0.3);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8), 0 0 20px rgba(255, 215, 0, 0.2);
    backdrop-filter: blur(8px);
    overflow: hidden;
    animation: slideInUp 0.3s ease-out;
    
    @keyframes slideInUp {
      from {
        opacity: 0;
        transform: translateY(30px) scale(0.95);
      }
      to {
        opacity: 1;
        transform: translateY(0) scale(1);
      }
    }
  }
  
  .dialog-header {
    padding: 16px 20px;
    border-bottom: 1px solid rgba(255, 215, 0, 0.2);
    background: linear-gradient(135deg, rgba(255, 215, 0, 0.1), rgba(255, 215, 0, 0.05));
    
    h3 {
      color: #FFD700;
      margin: 0;
      font-size: 16px;
      font-weight: 600;
      text-align: center;
    }
  }
  
  .dialog-body {
    padding: 24px 20px;
    
    .alone-room-message {
      display: flex;
      align-items: flex-start;
      gap: 16px;
      
      .message-icon {
        font-size: 32px;
        filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
        flex-shrink: 0;
      }
      
      .message-text {
        flex: 1;
        
        .main-message {
          color: white;
          font-size: 16px;
          font-weight: 600;
          margin: 0 0 8px 0;
          line-height: 1.4;
        }
        
        .sub-message {
          color: rgba(255, 255, 255, 0.8);
          font-size: 14px;
          margin: 0;
          line-height: 1.5;
        }
      }
    }
  }
  
  .dialog-footer {
    display: flex;
    gap: 12px;
    padding: 16px 20px;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.3);
    
    .cancel-btn, .confirm-btn {
      flex: 1;
      padding: 12px 16px;
      border-radius: 8px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 600;
      transition: all 0.3s ease;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 6px;
      
      &:hover {
        transform: translateY(-1px);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
      }
      
      &:active {
        transform: translateY(0);
      }
    }
    
    .cancel-btn {
      background: rgba(108, 117, 125, 0.8);
      border: 1px solid rgba(108, 117, 125, 0.6);
      color: white;
      
      &:hover {
        background: rgba(108, 117, 125, 1);
        border-color: rgba(108, 117, 125, 0.8);
      }
    }
    
    .confirm-btn {
      background: linear-gradient(135deg, #4CAF50, #45a049);
      border: 1px solid #4CAF50;
      color: white;
      box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
      
      &:hover {
        background: linear-gradient(135deg, #45a049, #3d8b40);
        border-color: #45a049;
        box-shadow: 0 4px 16px rgba(76, 175, 80, 0.4);
      }
      
      &.single-btn {
        flex: none;
        min-width: 120px;
        margin: 0 auto;
      }
    }
  }
  
  // 移动端适配
  @media (max-width: 768px) {
    padding: 16px;
    
    .dialog-content {
      width: 95vw;
      max-width: none;
      border-radius: 10px;
    }
    
    .dialog-header {
      padding: 14px 16px;
      
      h3 {
        font-size: 15px;
      }
    }
    
    .dialog-body {
      padding: 20px 16px;
      
      .alone-room-message {
        gap: 12px;
        
        .message-icon {
          font-size: 28px;
        }
        
        .message-text {
          .main-message {
            font-size: 15px;
          }
          
          .sub-message {
            font-size: 13px;
          }
        }
      }
    }
    
    .dialog-footer {
      padding: 14px 16px;
      gap: 10px;
      
      .cancel-btn, .confirm-btn {
        padding: 10px 14px;
        font-size: 13px;
      }
    }
  }
}
</style>